Promises are currently one of the best tools JavaScript has to offer to keep track of all the asynchronous calls in a program. If you don’t use them already you definitely should start. But in this post I want to share a technique which even if its dead simple that wasn’t quite obvious how to achieve right away from the Promise documentation.

The parallel approach

The problem I had at hand was database operations. Several deletes which I wanted to be sure that all of them have completed before continuing . Which is quite easy to do with array of Promises like this :

simple.js function dbDelete(data) { console.log("Delete ", data); } var promises = []; for(var id of [1,2,3]){ (function(id){ promises.push(new Promise(function(resolve, reject) { // Use setTimeout to simulate real DB operation taking time setTimeout(function () { dbDelete(id); resolve() }, 500+ Math.floor(Math.random()*500) ); })); })(id) } Promise.all(promises).then(function(){ console.log("All done."); }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function dbDelete ( data ) { console . log ( "Delete " , data ) ; } var promises = [ ] ; for ( var id of [ 1 , 2 , 3 ] ) { ( function ( id ) { promises . push ( new Promise ( function ( resolve , reject ) { // Use setTimeout to simulate real DB operation taking time setTimeout ( function ( ) { dbDelete ( id ) ; resolve ( ) } , 500 + Math . floor ( Math . random ( ) * 500 ) ) ; } ) ) ; } ) ( id ) } Promise . all ( promises ) . then ( function ( ) { console . log ( "All done." ) ; } ) ;

This works fine generally, but in my case these delete operations were kind of heavy, and were putting a lot of load on the database. Starting all 3 of them at the same time is not helping at all.

A not so working serial execution

So I decided to run them serially instead of parallel with the obvious approach:

serial.js function dbDelete(data) { console.log("Delete ", data); } new Promise(function(resolve, reject) { setTimeout(function () { dbDelete(1); resolve() }, 500+ Math.floor(Math.random()*500) ); }).then(function(){ setTimeout(function () { dbDelete(2); }, 500+ Math.floor(Math.random()*500) ); }).then(function(){ setTimeout(function () { dbDelete(3); }, 500+ Math.floor(Math.random()*500) ); }).then(function(){ console.log("All done."); }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function dbDelete ( data ) { console . log ( "Delete " , data ) ; } new Promise ( function ( resolve , reject ) { setTimeout ( function ( ) { dbDelete ( 1 ) ; resolve ( ) } , 500 + Math . floor ( Math . random ( ) * 500 ) ) ; } ) . then ( function ( ) { setTimeout ( function ( ) { dbDelete ( 2 ) ; } , 500 + Math . floor ( Math . random ( ) * 500 ) ) ; } ) . then ( function ( ) { setTimeout ( function ( ) { dbDelete ( 3 ) ; } , 500 + Math . floor ( Math . random ( ) * 500 ) ) ; } ) . then ( function ( ) { console . log ( "All done." ) ; } ) ;

Just chaining the promises with .then() doesn’t quite work as the .then() handlers are invoked all at the same time so once the first delete operation is resolved, the next two are again started simultaneously. Thanks to Quabouter , this is more clear now. The return value of a .then() is passed to Promise.resolve df and the resulting promise is used to resolve the next then(). That’s why returning simple value ( or no value ) will fire the next .then() while a returning a promise will block until it is resolved.

I had to search for a different approach

The final solution

According to .then() documentation it returns a new Promise, which makes possible for chaining .then() calls what is not quite clear is that when the function passed to then returns a new Promise it is used to fulfil the Promise returned by then(). With this knowledge it is possible to rewrite the loop like this :

serial2.js function dbDelete(data) { console.log("Delete ", data); } var promise = Promise.resolve(); for(var id of [1,2,3]){ (function(id){ promise = promise.then(function() { return new Promise(function(resolve, reject) { // Use setTimeout to simulate real DB operation taking time setTimeout(function () { dbDelete(id); resolve() }, 500+ Math.floor(Math.random()*500) ); })}); })(id); } promise.then(function(){ console.log("All done."); }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function dbDelete ( data ) { console . log ( "Delete " , data ) ; } var promise = Promise . resolve ( ) ; for ( var id of [ 1 , 2 , 3 ] ) { ( function ( id ) { promise = promise . then ( function ( ) { return new Promise ( function ( resolve , reject ) { // Use setTimeout to simulate real DB operation taking time setTimeout ( function ( ) { dbDelete ( id ) ; resolve ( ) } , 500 + Math . floor ( Math . random ( ) * 500 ) ) ; } ) } ) ; } ) ( id ) ; } promise . then ( function ( ) { console . log ( "All done." ) ; } ) ;

This gives the serial execution of dbDelete, where the next operation starts only after the previous has finished.

Hope this helps somebody 🙂

Share this: Facebook

LinkedIn

Reddit

Twitter

