Serial Promise Execution with JavaScript

This article will discuss executing promises serially (wait for one to complete before moving on to the next). This is an extremely common pattern if you are inside of a loop and want to process something one at a time.

For instance some how to do the following:

for(let i = 0; i < 10; i++) {  
   // doSomethingAsync
}

To start with, lets define a sample doSomethingAsync function. doSomethingAsync will wait one second and then print a number and resolve.

function doSomethingAsync(i) {  
  return new Promise((resolve) => {
    setTimeout(() => { console.log(i); resolve(); }, 1000);
  });
}

Lets consider what it looks like to chain this function. If we wanted the function to print

1  
2  
3  
complete  

we would chain the promises as such.

Promise.resolve()  
  .then(() => doSomethingAsync(1))
  .then(() => doSomethingAsync(2))
  .then(() => doSomethingAsync(3))
  .then(() => console.log('complete'));

But what if we wanted to do this with a loop? Dynamic promise chaining requires a bit more thought. So let's break it down a bit.

Instead of using fluent code, we'll use variable reassignment to chain the evaluation of each promise.

let chain = Promise.resolve();  
chain = chain.then(() => doSomethingAsync(1));  
chain = chain.then(() => doSomethingAsync(2));  
chain = chain.then(() => doSomethingAsync(3));  
chain.then(() => console.log('complete'));  

With this code, chain represents the promise generated by the prior step. Remember that then actually returns a new Promise. This means, applying chain.then(fn) will create a new promise and we reassign chain to be equal to the new Promise.

We also use an arrow function to lazily evaluate the doSomethingAsync function. This is extremely important. If you do not wrap doSomethingAsync in a function expression, they will execute immediately.

The last step is taking the above code and putting it in a loop. We will collapse the manual steps of reassignment and put it inside a loop.

let vals = [1,2,3];  
let chain = Promise.resolve();  
for(let val of vals) {  
  chain = chain.then(() => doSomethingAsync(val));
}
chain.then(() => console.log('complete'));  

Pretty cool right? That's all there is to it.

Ok, bonus point. A simpler, and preferred method is using async/await. This is only natively support in Node 7+. So, things like AWS Lambda functions can't use it, unless you transpile with Babel. But look how simple the code is...

let vals = [1,2,3];  
for(let val of vals) {  
  await doSomethingAsync(val);
}
console.log('complete');  
comments powered by Disqus