Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I guarantee resolution order of multiple promises?

Trying to learn some modern JS and in particular the ECMAScript 6 Promises. I'm playing with this simple test:

let slow = new Promise((resolve) => {
  setTimeout(function()
  {  
	console.log('slow');
	resolve();	
  }, 2000, 'slow');
});

let instant = new Promise((resolve) => {
	console.log('instant');
	resolve();	
});

let quick = new Promise((resolve) => {
  setTimeout(function()
  {  
	console.log('quick');
	resolve();	
  }, 1000, 'quick');
});

Promise.all([slow, instant, quick]).then(function(results) {
  console.log('finished');
}, function(error) {
  console.log(error);	
});

What I want here is to start all Promises async at the same time. And log when they are all finished. In the console this shows as expected: "instant", "quick", "slow" and "finished".

However what if I wanted to be sure that "instant" doesn't echo/log before "slow" has completed? That is, I want the console to log "quick", "slow", "instant" and "finished"...but at the same time, they must still all start at the same time async.

How can I achieve this?

like image 585
Werner Avatar asked Oct 25 '16 19:10

Werner


2 Answers

So to be clear, what you want to do here is kick off all the promises at once and display the results of each promise in a particular order as they come in, correct?

In that case, I'd probably do it like this:

let slow = new Promise((resolve) => {
  setTimeout(function()
  {
    // Rather than log here, we resolve to the value we want to log
    resolve('slow');
  }, 2000, 'slow');
});

let instant = new Promise((resolve) => {
    resolve('instant');  
});

let quick = new Promise((resolve) => {
  setTimeout(function()
  {  
    resolve('quick');  
  }, 1000, 'quick');
});

// All Promises are now running. Let's print the results...

// First wait for the result of `slow`...
slow.then((result) => {
  // Result received...
  console.log(result);
  
  // Now wait for the result of instant...
  instant.then((result) => {
    
    // Result received...
    console.log(result);
    
    // Now wait for the result of quick...
    quick.then((result) => {
      
      // Result received...
      console.log(result);
      
    }).then((result) => {
      // Done
      console.log('finished');
    });
  });
});

Notice that unlike cchamberlain's answer, this method does not wait for all promises to resolve before it starts returning results. It returns the results as they come in, but without violating your requirement of keeping the results in-order. (To verify this, try changing the wait time of quick to 2500ms, and observe that its result is printed 500ms after instant.) Depending on your application, this may be desirable.

The above code is a bit messy, but thankfully with the new async/await syntax in ES2017 it can be made much cleaner:

let slow = new Promise((resolve) => {
  setTimeout(function()
  {
    // Rather than log here, we resolve to the value we want to log
    resolve('slow');
  }, 2000, 'slow');
});

let instant = new Promise((resolve) => {
    resolve('instant');  
});

let quick = new Promise((resolve) => {
  setTimeout(function()
  {  
    resolve('quick');  
  }, 1000, 'quick');
});

// All Promises are now running. Let's print the results...

async function logResults(...promises) {
  for (let promise of promises) {
    console.log(await promise);
  }
}

logResults(slow, instant, quick).then(() => console.log('finished'));

Try in Babel. Note: The above code doesn't currently work in modern browsers without Babel (as of October 2016). In future browsers it will.

like image 64
Ajedi32 Avatar answered Oct 26 '22 15:10

Ajedi32


Part of the issue is the logging is happening in the setTimeout method, and not actually from the promise resolution.

const slow = new Promise((resolve) => {
  setTimeout(() => {
    console.log('slow - from setTimeout');
    resolve('slow - from resolve');
  }, 2000, 'slow');
});

const instant = new Promise((resolve) => {
  console.log('instant - from setTimeout');
  resolve('instant - from resolve');
});

const quick = new Promise((resolve) => {
  setTimeout(() => {
    console.log('quick - from setTimeout');
    resolve('quick -from resolve');
  }, 1000, 'quick');
});

Promise.all([slow, instant, quick]).then((results) => {
  console.log(results);
  console.log('finished');
}, (error) => {
  console.log(error);
});

Passing in the value to the resolve method will return everything in the Promise.all. The response comes back from each promise as an array, and you can iterate through those responses once all are complete.

like image 26
Kelly J Andrews Avatar answered Oct 26 '22 17:10

Kelly J Andrews