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');