Sequential Execution of JavaScript Promises

js logo

JavaScript iterator functions like forEach or map guarantee the order of their returned values. However, they do not guarantee the execution order of callback functions.

Generally speaking, it's better if you could run heavy Promise functions in parallel. But sometimes we need to guarantee the execution order for some processes like batches. If you simply use forEach for purposes as such, you would most likely get unexpected results.

So let's figure out how to sequentially execute an array of Promises.

Strategy

To create Promise.then chains with the reduce function which guarantees the sequential execution order of callbacks.

Test

  • To generate 10 files from an array of [0…9] using the File System of Node.js.
  • Because it's easy to create an async code with fs.
  • We will run Promised write() with forEach(), Promise.all() + map(), Promise + reduce() respectively and check the results.

write function

It's a simple Promise wrapper of appendFile: append if the file exists, create one if not.

Practically speaking, you can use appendFileSync for this but we are just experimenting here so…

Execution Command

$ node -e "require('./promise_array_test.js').EXPORTED_FUNCTION()"

We will execute each target function directly from CLI. (-e is for JS execution.)

Vanilla forEach()

A regular forEach. The lambda function will be executed in parallel as you can expect.

Result

$ node -e "require('./promise_array_test.js').forEachTest()"
just forEach ===========
done
0 is done!
2 is done!
1 is done!
8 is done!
6 is done!
5 is done!
7 is done!
4 is done!
9 is done!
3 is done!

done is called first, then others are parallelly called in the random order.

Promise.all + map()

Promise.all takes an array of Promises and execute them in parallel, then returns the result in the initial order. (n array of Promises are created by returning the Promised write function, and passed to Promise.all on line 5.

Result

$ node -e "require('./promise_array_test.js').promiseAllTest()"
promise all ============
1 is done!
0 is done!
5 is done!
7 is done!
4 is done!
2 is done!
3 is done!
8 is done!
6 is done!
9 is done!
done

As we have the then chains of Promise.all, done is printed after all callbacks are executed.

Promise + reduce()

reduce()operates each element of an array sequentially.

The callback's first argument is the return value of the previously executed function, and the second arg is the current value. As this is one the JS iterator functions, the third argument is the index of the element.

Notably, reduce takes an initial value for the second argument. In other words, you can pass the first value that will be in the first arg of the callback: acc in the example below.

reduce((acc, cur) \=> acc + cur, 10);

If you didn't pass an initial value, the first callback will take 0 for acc, 1 for cur, and the index would be 1. So please be aware of the behavior.

Enough with the function behavior explanation. Let's move on to the “creating lambda then chains by passing a Promise to acc” implementation.

We can start the callback execution like acc.then() by passing Promise.resolve() as the initial value.

(Just in case you are wondering, Promise.resolve() is the same as new Promise((res, rej) => res()). Promise.reject() works in the same manner.)

Result

$ node -e "require('./promise_array_test.js').promiseReduceTest()"
promise reduce ===========
0 is done!
1 is done!
2 is done!
3 is done!
4 is done!
5 is done!
6 is done!
7 is done!
8 is done!
9 is done!
done

They are beautifully executed in sequential order.

The Whole Test Code

References

COPYRIGHT © 2023 Kohei Ando