Do multiple .fetch() promises

18,133

Solution 1

You are returning from the then handler after first response, instead what you need to do is to return the list of blobs:

Promise
.all(promises)
.then(function(response) {
    // CHANGED HERE
    var blobPromises = [];
    for (var i = response.length - 1; i >= 0; i--) {
        blobPromises.push(response[i].blob());
    }
    return Promise.all(blobPromises);
})
.then(function(blob) {
    console.log(blob.length);
    for (var i = blob.length - 1; i >= 0; i--) {
        lcl_images[i].value = URL.createObjectURL(blob[i]);
        document.getElementById(lcl_images[i].id).src = objectURL;
    }
})
.catch(function(error) {
    console.log(error);
});

Solution 2

It is a general rule that a wholly synchronous intermediate step in the success path of a promise chain can be amalgamated with the next step, allowing one then() to be omitted from the chain.

There is actually a proviso on that statement, involving intermediate catches, but it will suffice for this answer.

So, if the .blob() method is geuinely synchronous (it returns a value), only one .then() is required, not two.

Here are two approaches, both of which exploit Array.prototype.map(), and both should work (though they will differ under error conditions):

1. Simple .map() with detail in Promise.all()

var promises = values.reverse().map(fetch); // you may need .reverse(), maybe not. I'm not 100% sure.

return Promise.all(promises).then(function(responses) {
    responses.forEach(function(r, i) {
        var imageObj = lcl_images[i],
            element = document.getElementById(imageObj.id);
        imageObj.value = URL.createObjectURL(r.blob());
        if(element) { //safety
            element.src = imageObj.value;
        }
    });
    return responses; // here, return whatever you want to be made available to the caller.
}).catch(function(error) {
    console.log(error);
});

If you prefer, you can write :

return Promise.all(values.reverse().map(fetch)).then(function(responses) {
    // ...
});

2. Detail in .map() followed a simple Promise.all()

var promises = values.reverse().map(function(val, i) {
    return fetch(val).then(function(result) {
        var imageObj = lcl_images[i],
            element = document.getElementById(imageObj.id);
        imageObj.value = URL.createObjectURL(result.blob());
        if(element) { //safety
            element.src = imageObj.value;
        }
        return result; // here, return whatever you want to be made available to the caller.
    });
});

return Promise.all(promises).catch(function(error) { // return a promise to the caller
    console.log(error);
}); 

Notes:

  • (1) will fail completely if any one fetch() fails.
  • (2) will perform all the imageObj.value ... and element.src = ... stuff for all successful fetches even if one or more fetch()... fails. Any single failure will cause Promise.all(promises) to return a rejected promise.
  • (1) or (2) may be more appropriate depending on what you want.
  • There are other error handling possibilities.
  • If neither approach works, then the most reasonable explanation would be that the .blob() method returns a promise, not a value.
Share:
18,133
FrancescoN
Author by

FrancescoN

Updated on August 11, 2022

Comments

  • FrancescoN
    FrancescoN over 1 year

    I want to fetch multiple images and turn them in blob. I'm a newbie about promises, I've tried but I can't get through.

    Here below, a single .fetch() promise

    fetch('http://cors.io/?u=http://alistapart.com/d/_made/d/ALA350_appcache_300_960_472_81.jpg')
        .then(function(response) {
            return response.blob();
        })
        .then(function(myBlob) {
            var objectURL = URL.createObjectURL(myBlob);
            document.getElementById('myImage').src = objectURL;
        });
    

    Now multiple .fetch() promise (don't work)

    var promises = [];
    
    for (var i = values.length - 1; i >= 0; i--) {
        promises.push(fetch(values[i]));
    }
    
    Promise
        .all(promises)
        .then(function(response) {
            for (var i = response.length - 1; i >= 0; i--) {
                return response[i].blob();
            }
        })
        .then(function(blob) {
            console.log(blob.length); //undefined !!!
            for (var i = blob.length - 1; i >= 0; i--) {
                console.log(blob[i]);
                lcl_images[i].value = URL.createObjectURL(blob[i]);
                document.getElementById(lcl_images[i].id).src = objectURL;
            }
        })
        .catch(function(error) {
            console.log(error);
        });
    
    • Roamer-1888
      Roamer-1888 almost 8 years
      So what does the .blob() method return, a blob or a promise?
    • FrancescoN
      FrancescoN almost 8 years
      .blob() returns a blob(), but if you're doing multiple promise, you have to return Promise.all(blobs) in .then(response). After this, in .then(blob) -> blob is array of blob objects
    • Roamer-1888
      Roamer-1888 almost 8 years
      OK, so if .blob() is synchronous, then you don't need two .thens. Promise.all(promises).then(function(responses) {...}).catch(...);will do the job without ever needing to create an array of blobs.
    • FrancescoN
      FrancescoN almost 8 years
      if you try yourself this won't work, I'm a newbie about promise. I've tried several times doing what you said, but you have to return the promise response[i].blob() and in the next .then you'll have the blob object.
    • Roamer-1888
      Roamer-1888 almost 8 years
      I'll write an answer for you.
  • FrancescoN
    FrancescoN almost 8 years
    I've edited your solution and added console.log(), you can clearly see the problem: I don't have blob objects in the blob promise, this is why it raises the error: TypeError: Failed to execute 'createObjectURL' on 'URL': No function was found that matched the signature provided.(…)
  • nlawson
    nlawson almost 8 years
    When you return blobs, you end up with blobs in the next function, not blob. blob.length is undefined because it's an array of blobs, not a single blob.
  • nlawson
    nlawson almost 8 years
    Also in this case, your blobs are actually an array of promises for blobs, meaning you need to return Promise.all(blobs). Really you should rename that blobs to blobPromises or something.
  • Roamer-1888
    Roamer-1888 almost 8 years
    If that's the API being called, then it's cool. Any object other than a promise is OK here.