Sequential and Parallel Asynchronous Functions
JavaScript is known for its non-blocking calls through the use of callbacks, promises, and async/await.1 Occasionally the need will arise to process a collection of non-blocking calls either sequentially or in parallel.
Set Up
Let's say we need to make a collection of API calls. Our mock api will be a function that will return a promise that will resolve after 1 second.
const apiEndpoints = ["first", "second", "third"];
const apiCall = endpoint => new Promise(resolve => setTimeout(resolve, 1000));
Parallel Processing
In this case, the execution order for the API calls are not important, we just need to know when they have all completed.
const parallel = Promise.all(apiEndpoints.map(apiCall));
The map method is used on the apiEndpoints
array and each value is replaced by our apiCall
promise. Once each promise resolves, Promise.all
2 resolves.
Sequential Processing
In this case, the order of execution is important. Each API call must wait for the previous to return.
const reduceApiEndpoints = async (previous, endpoint) => {
await previous;
return apiCall(endpoint);
};
const sequential = apiEndpoints.reduce(reduceApiEndpoints, Promise.resolve());
The reduce method3 is used on apiEndpoints
so that we can take advantage of the reduce accumulator that is passed in as the first variable in the callback function. In this example, we have named the accumulator previous
. The role of the accumulator is to pass the return value from the previous iteration into the next iteration. For the first iteration, a resolved promise is used as the initializer. We could have used any value for the initializer in this case, but in general, the initializer should be of the same type as the accumulator.
For the reduce callback, we await for the previous
promise to resolve and return an apiCall
, which is a promise. That promise is the passed onto the next iteration.
The promise given to sequential
will be resolved once the last promise in the array resolves.