简体   繁体   中英

Promise.all() is not resolving promises in the expected order

If I understand Promise.all() correctly, I would expect this code to take 5 seconds before outputting only the reason for the rejected promise to the console.

function firstAsyncFunction() {
  return new Promise(function(resolve, reject){
    setTimeout(function(){
      resolve(console.log('First async function has been resolved!'));
    }, 500);
  });    
}

function secondAsyncFunction() {
  return new Promise(function(resolve, reject) {
    setTimeout(function(){
      resolve(console.log('Second async function has been resolved!'));
    }, 2000);
  });
}

function thirdAsyncFunction() {
  return new Promise(function(resolve, reject) {      
    setTimeout(function() {              
      reject('Internal server error'); // rejected for some reason
    }, 5000);    
  });
};

Promise.all([secondAsyncFunction(), firstAsyncFunction(), thirdAsyncFunction()])
    .then(function(values){        
      values.forEach(function(value){
        console.log(value);
      });

    }).catch(function(err){
      console.log(err);
    });

Instead, the first two promises resolve, then the final promise rejects. Additionally, the first two promises aren't even resolving in the order in which they are passed in to Promise.all(). Do I need to compose my promises differently to see the behavior I'm expecting?

Edited

Promise.all() does indeed wait until all the promises in the iterable passed into it resolve. My first clue was the fact that the console was outputting undefined (thanks Jaromanda X). If I remove the calls to console.log() in the resolve to firstAsyncFunction() and secondAsyncFunction() the following piece of code works exactly as I would expect:

function firstAsyncFunction() {
  return new Promise(function(resolve, reject){
    setTimeout(function(){
      resolve('First async function has been resolved!');
    }, 1000);
  });    
}

function secondAsyncFunction() {
  return new Promise(function(resolve, reject) {
    resolve('Second async function has been resolved!');      
  });
}

function thirdAsyncFunction() {
  return new Promise(function(resolve, reject) {      
    setTimeout(function() {      
      reject('Internal server error');
    }, 5000);
  });
};

Promise.all([
  thirdAsyncFunction(), 
  firstAsyncFunction(), 
  secondAsyncFunction()
])
 .then(function(values){        
   values.forEach(function(value){
     console.log(value);
   });
 })
 .catch(function(err){
   console.log(err);
 });

After five seconds I see "Internal server error" only. Promise.all() rejects the other promises even though they resolve sooner than the promise that was rejected. And yes, the values resolved by Promise.all() will be in the same order as the promises in the iterable passed in as a parameter. Thanks for all your help!

One more example:

function firstAsyncFunction() {
  return new Promise(function(resolve, reject){
    setTimeout(function(){
      resolve('First async function has been resolved!');
    }, 1000);
  });    
}

function secondAsyncFunction() {
  return new Promise(function(resolve, reject) {
    resolve('Second async function has been resolved!');      
  });
}

function thirdAsyncFunction() {
  return new Promise(function(resolve, reject) {      
    setTimeout(function() {
      resolve({
        users: [
          { name: 'Ronnie', age: 22 },
          { name: 'Bobby', age: 21 },
          { name: 'Ricky', age: 21 },
          { name: 'Mike', age: 20 }
        ]
      });
    }, 5000);
  });
};

Promise.all([thirdAsyncFunction(), firstAsyncFunction(), secondAsyncFunction()])
  .then(function(values){        
    values.forEach(function(value){
      console.log(value);
    });
  })
  .catch(function(err){
    console.log(err);
  });

After five seconds, this code will output:

{ users: 
   [ { name: 'Ronnie', age: 22 },
     { name: 'Bobby', age: 21 },
     { name: 'Ricky', age: 21 },
     { name: 'Mike', age: 20 } ] }
First async function has been resolved!
Second async function has been resolved!

Exactly what we want.

The then or the catch function of Promise.all is called only once all of the promises have resolved or one of the promises has been rejected. The individual promises can resolve in any order (that's the entire point of promises - they are asynchronous and can complete what they are supposed to whenever they like)

That said, it would be clearer if you tagged your Promise.all console.log differently, like so

Promise.all([secondAsyncFunction(), firstAsyncFunction(), thirdAsyncFunction()])
    .then(function (values) {
        values.forEach(function (value) {
            console.log('then:' + value);
        });

    }).catch(function (err) {
        console.log(err);
    });

You could be mixing up the console.logs in the promise and the one in the then callback.

Promise.all does not impose any order on the promises, they fulfill when they fulfill

the third promise "rejects" well after the other two "resolve", so the fact that the first two are resolved before the last is rejected is to be expected

by the way, your first two promises are resolving the value undefined and outputting to the console - which is why you may think that Promise.all is doing something it ought not be doing

Do I need to compose my promises differently to see the behavior I'm expecting?

You wont get exactly the behaviour you are expecting, because you expect the promises to resolve in a particular order, the only way that can happen is for the promise to wait for the previous one to be fulfilled before it can fulfill, so the time would be cumulative, not in parallel, so in that case you wont get the rejection for 7.5 seconds rather than the 5 seconds you "expect"

Just to add to what others have said.

Promise.all does not run sequentially.

Here is an example of running async functions sequentially in ES6

/**
 * Runs async functions sequentially
 * @param Function[]
 * @return Promise<any>
 */
function runSequentially(functions) {
    return functions.reduce((promise, next) => {
        return promise.then(next);
    }, Promise.resolve());
}


/**
 * Full Example
 */

function foo()
{
    return new Promise(( resolve, reject )=>{
        resolve();
    })
}

function boo() {
    return new Promise((resolve, reject) => {
        resolve();
    })
}

function baz() {
    return new Promise((resolve, reject) => {
        resolve();
    })
}

const functions = [foo, boo, baz];

runSequentially(functions).then((result) => {

}).catch((error) => {

});

Promise.all runs all Promises that you pass it in parallel. This is useful when the Promises don't depend on each other and you need to perform both. For instance, in an integration test where you need to register two clients with a server. You need to wait for both to finish, but launching both at the same time will cut your wait roughly in half.

If you want to run them sequentially, you could chain your Promises, returning the result from one to the next:

firstAsyncFunction
  .then(secondAsyncFunction)
  .then(thirdAsyncFunction)
  .then(function(values) {
    console.log(values)
  }, function(err) {
    console.log(err);
  });

If you're looking to return aggregated data from all three calls, you'd also want to modify your promises to return their value in an array (or object). For example:

  • firstAsyncFunction takes userId and returns { name: 'Bob' }
  • secondAsyncFunction takes userObject and returns { name: 'Bob', car: 'Porsche' }
  • thirdAsyncFunction takes userObject and returns { name: 'Bob', car: 'Porsche', kids: 5 }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM