How does promise.all work?

2019-01-28 01:40发布

问题:

I started diggin' in promises and found interesting Promise.all.

It is stated in MDN that

The Promise.all(iterable) method returns a promise that resolves when all of the promises in the iterable argument have resolved.

Which basically means that set promises resolve after and if all promises in argument list have been resolved. I tried to implement it. I made simply promise ajax call.

var get = function(url) {
    return new Promise(function(resolve,reject) {
        var xhtml=new XMLHttpRequest();

        xhtml.open("GET",url);
        xhtml.responseType = 'blob';
        xhtml.onload = function() {
            if(xhtml.status==200){
                resolve(xhtml.response);
            } else {
                reject(Error("Error"+statusText));
            }
        }
        xhtml.send();
    });

}

get("one.jpg").then(function(response){
    var blob = window.URL.createObjectURL(response);
    var img = document.createElement("img");

    console.log("Success"+response);

    img.src = blob;

    document.body.appendChild(img);
});

Which works fine. But after I tried to add Promise.all it threw an error.

Promise.all(get).then(function(response){alert("done")});

this as i said threw an error " Argument 1 of Promise.all can't be converted to a sequence." So I assume i didn't get the meaning of promise.all. How does it work?

回答1:

Promise.all takes an array (or any iterable) of promises and fulfills when all of them fulfill or rejects when one of them rejects. I think it's easier to understand if we implement it and understand why we need it.

A common use case might be to wait for the window to load and for the server to return data in order to run some code:

// a function that returns a promise for when the document is ready.
function windowReady(){
    return new Promise(function(resolve){
         window.addEventListener('DOMContentLoaded', resolve);
    }); 
}

// function that returns a promise for some data
function getData(){
    return fetch("/").then(function(r){ return r.json() });
}

Now, we want both of them to execute at the same time and then get the result. There are two items here but there could have easily been 5 things to wait for, or 100. So we use Promise.all:

Promise.all([windowReady(), getData()]).then(function(results){
     // results[1] is the data, it's all in an array.
});

Let's see how we can implement it:

function all(iterable){ // take an iterable 
  // `all` returns a promise.  
  return new Promise(function(resolve, reject){ 
    let counter = 0; // start with 0 things to wait for
    let results = [], i;
    for(let p of iterable){
        let current = i;
        counter++; // increase the counter 
        Promise.resolve(p).then(function(res){ // treat p as a promise, when it is ready: 
          results[i] = res; // keep the current result
          if(counter === 0) resolve(results) // we're done
        }, reject); // we reject on ANY error
       i++; // progress counter for results array
    }
  });
}

Or, in even more ES6ness:

let all = iterable => new Promise((resolve, reject) => { 
  let arr = [...iterable], c = arr.length, results = [];
  arr.map(Promise.resolve, Promise).
      map((p, i) => p.then(v => { 
        r[i] = v;
        if(--c === 0) resolve(r);
      } , reject));
});


回答2:

Your get function returns a Promise. You are just passing a reference to the get function. You have to pass an array of Promises

Promise.all([get("one.jpg")]).then(...);


回答3:

TLDR:

Promise.all is a Javascript method that takes an iterable (e.g. Array) of promises as an argument and returns a single promise when all the promises in the iterable argument have been resolved (or when iterable argument contains no promises). It resolves with an array of the resolved values and rejects with a single value of the first rejected Promise.

Example:

var promise1 = Promise.resolve(5);
var promise2 = Math.random() > 0.5? 1 : Promise.reject(1); // either resolves or rejects
var promise3 = new Promise((resolve, reject) => {
  setTimeout(() => resolve('foo'), 1000);
});

Promise.all([promise1, promise2, promise3]).then((val) => {
  console.log(val);
}).catch((val) => {
  console.log(val);
});

In the above example 3 Promises are passed into the Promise.all function as an array. Promise 1 and 3 always resolve. Promise 2 either resolves or rejects based on the random Nr generator. This Promise.all method then returns a resolved or rejected Promise based on the random Nr generator.

Then the then() method and the catch() method can be called on this promise which is returned from Promise.all. The then() method gets an array of all the resolved values, [5, 1, 'foo'] in this case. The catch() method gets the value of the first rejected Promise, 1 in this example.

When to use:

This method is very usefull when you want to execute multiple async operations and need to something with the results after the async operations. When using Promise.all all promises can be processed at the same time while still getting to operate on all the incoming data.

For example, when we need to get information using multiple AJAX requests and combine the data to something useful. It is essential to wait for all the data to be available otherwise we would try to combine non existing data which would lead to problems.