Back to Basics: Running Promises in Serial with Array.reduce()

Running JavaScript Promises in parallel is as simple as calling Promise.all() with an array of Promises. But sometimes you need to run then in sequential order. What then?

// Parallel
return Promise.all([
    task1,
    task2,
    task3,
]).then(arrayOfResults => {
    // Do something with all results
});

We could trivially chain these promises together in serial fashion with .then()

// Serial
return task1.then(task2).then(task3);

However this is only feasible when you can hardcode tasks. It also prevents you from capturing the return values from task1 and task2.

You can modify the code to capture return values:

// Serial
return task1.then(result1 =>
    task2.then(result2 =>
        task3.then(result3 =>
            [ result1, result2, result3 ]
        )
    )
).then(arrayOfResults => {
    // Do something with all results
});

Once again, this is fine for fixed tasks but doesn’t help us when our tasks are dynamic.

reduce() to the Rescue

We’re going to use the Array.reduce() function to collapse the array of promises into a single promise chain. At a high level, it will look like this:

// Serial
return [
    task1,
    task2,
    task3,
].reduce( /* TODO */ );

Check out Array.prototype.reduce() on MDN if you need a refresher.

Basically, reduce() has two important components. First, we pass in a function, which takes the previous value and the current value. And, second, we pass the initial value, which takes the place of the ‘previous’ value for the first array element.

// Serial
return [
    task1,
    task2,
    task3,
].reduce((promiseChain, currentTask) => {
    // Note: promiseChain === initialPromise
    // on the first time through this function
    /* TODO */
}, initialPromise);

Thinking about our reducer function, we want to execute the promise chain and then execute the current task. e.g.

(promiseChain, currentTask) => {
    return promiseChain.then(currentTask);
}

But we don’t want to lose the return values from the promise chain. So we need to propagate the return values.

(promiseChain, currentTask) => {
    return promiseChain.then(chainResults =>
        currentTask.then(currentResult =>
            [ ...chainResults, currentResult ]
        )
    );
}

And, finally, we need an initial value for promiseChain. We’ll use Promise.resolve([]) for this. It’s a promise that immediately resolves to an empty array…

The Final Code

const tasks = getTaskArray();

return tasks.reduce((promiseChain, currentTask) => {
    return promiseChain.then(chainResults =>
        currentTask.then(currentResult =>
            [ ...chainResults, currentResult ]
        )
    );
}, Promise.resolve([])).then(arrayOfResults => {
    // Do something with all results
});

Further Reading

Want Modern web development to be easier?

I've got lots of tools, tips, best practices, and code to share with you.
Sign up on my list to be notified when new posts are published.

(100% spam free and one-click unsubscribe)

Back to Basics: Running Promises in Serial with Array.reduce()

3 thoughts on “Back to Basics: Running Promises in Serial with Array.reduce()

    1. james says:

      Thanks for replying!!
      The only reason I was considering redux-observable is that much of what you learn can be taken outside of redux/react etc.. the whole reactive thought processes and usages etc.. Saga, not so much as it abtracts the generator/iterator logic completely away.

Leave a Reply

Your email address will not be published. Required fields are marked *