The reduce
function iteratively constructs a value and returns it, which is not necessarily a collection. That's where the name comes from, as it reduces a collection to a value.
The iteratee function gets the previous result, called memo
in the examples below, and the current value, e
.
The following function sums the elements, starting with 0 (the second argument of reduce
):
const arr = [1, 2, 3];
const syncRes = arr.reduce((memo, e) => {
return memo + e;
}, 0);
console.log(syncRes);
// 6
memo | e | result |
---|---|---|
0 (initial) | 1 | 1 |
1 | 2 | 3 |
3 | 3 | (end result) 6 |
An async version is almost the same, but it returns a Promise on each iteration, so memo
will be the Promise of the previous result. The iteratee function needs to await
it in order to calculate the next result:
// utility function for sleeping
const sleep = (n) => new Promise((res) => setTimeout(res, n));
const arr = [1, 2, 3];
const asyncRes = await arr.reduce(async (memo, e) => {
await sleep(10);
return (await memo) + e;
}, 0);
console.log(asyncRes);
// 6
memo | e | result |
---|---|---|
0 (initial) | 1 | Promise(1) |
Promise(1) | 2 | Promise(3) |
Promise(3) | 3 | (end result) Promise(6) |
With the structure of async (memo, e) => await memo
, the reduce
can handle any async functions and it can be await
ed.
Concurrency has an interesting property when it comes to reduce
. In the synchronous version, elements are processed one-by-one, which is not surprising as they rely on the previous result. But when an async reduce
is run, all the iteratee functions start running in parallel and wait for the previous result (await memo
) only when needed.
In the example above, all the sleep
s happen in parallel, as the await memo
, which makes the function to wait for the previous one to finish, comes later.