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

JavaScript

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?

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

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

// Serialreturn 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:

// Serialreturn 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:

// Serialreturn [    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.

// Serialreturn [    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

New to web development? Not sure where to go or what to do next?

Sign up on my email list where I write about modern web development with technology like React, Redux, TypeScript, Webpack and more.