Sequential Execution of JavaScript Promises
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()
withforEach()
,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 ===========done0 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.