How to run JavaScript promises in parallel

Reading Time: 4 minutes 🕑

Last updated: September 27, 2022.

One of the great things about JavaScript promises is flexibility in how they are run.

Unlike callbacks, which are always executed sequentially (one after another), you have options for how to run multiple promises.

Promises provide you with several options for how you can run promises in parallel.

Table of contents

Creating promises

To demonstrate, we need multiple promises.

Here are three that will resolve successfully at different time points:

/* Creating three promises */

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => { resolve("Fast") }, 500);
});
const promise2 = new Promise((resolve, reject) => {
  setTimeout(() => { resolve("Slow") }, 2000);
});
const promise3 = new Promise((resolve, reject) => {
  setTimeout(() => { resolve("Middling") }, 1000);
});

Running promises in parallel

Option 1: Individually

One way is to simply handle the results of each promise individually:

/* Handling the promises individually */

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => { resolve("Fast") }, 500);
});
const promise2 = new Promise((resolve, reject) => {
  setTimeout(() => { resolve("Slow") }, 2000);
});
const promise3 = new Promise((resolve, reject) => {
  setTimeout(() => { resolve("Middling") }, 1000);
});

promise1
  .then(res => console.log(res)) // "Fast" (after 0.5 seconds)
  .catch(err => console.log(err));

promise2
  .then(res => console.log(res))  // "Slow" (after 2 seconds)
  .catch(err => console.log(err));

promise3
  .then(res => console.log(res))  // "Middling" (after 2 seconds)
  .catch(err => console.log(err));

A feature of running promises in parallel individually like this is that the result of each promise is returned as soon as it is complete.

And, if a promise fails, it does not affect the other promises, since all are executing individually.

Option 2: All or nothing with Promise.all

Another option is to handle the promises together with Promise.all. This is a good option if the promises you want to run are part of a larger process.

To handle the promises together, first store them in an array. Then, call the .all() method on the Promise object, passing in the array of promises.

You can then handle the result using .then and .catch syntax:

/* Handling promises together Promise.all() */

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => { resolve("Fast") }, 500);
});
const promise2 = new Promise((resolve, reject) => {
  setTimeout(() => { resolve("Slow") }, 2000);
});
const promise3 = new Promise((resolve, reject) => {
  setTimeout(() => { resolve("Middling") }, 1000);
});

const myPromiseArray = [promise1, promise2, promise3];

Promise.all(myPromiseArray)
.then(res => console.log(res)) // ["Fast","Slow","Middling"] (after two seconds)
.catch(err => console.log(err));

Handling with async-await syntax

Promise.all can also be nicely combined with async-await syntax:

/* Promise.all() with async-await syntax */

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => { resolve("Fast") }, 500);
});
const promise2 = new Promise((resolve, reject) => {
  setTimeout(() => { resolve("Slow") }, 2000);
});
const promise3 = new Promise((resolve, reject) => {
  setTimeout(() => { resolve("Middling") }, 1000);
});

const myPromiseArray = [promise1, promise2, promise3];

async function handleResult() {
  try {
    let res = await Promise.all(myPromiseArray);
    console.log(res); 
  } catch(e) {
    console.log(err);
  }
}

handleResult();

If a promise fails using Promise.all

One feature to be aware of when using Promise.all is that it ‘fails fast’.

If a single promise in the array fails, error-handling is immediately executed. And the result of none of the other promises is returned:

/* Promise.all() 'fails fast' when a promise rejects */

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => { reject(new Error("Failed")); resolve("Fast") }, 500);
});
const promise2 = new Promise((resolve, reject) => {
  setTimeout(() => { resolve("Slow") }, 2000);
});
const promise3 = new Promise((resolve, reject) => {
  setTimeout(() => { resolve("Middling") }, 1000);
});

const myPromiseArray = [promise1, promise2, promise3];

Promise.all(myPromiseArray)
.then(res => console.log(res))
.catch(err => console.log(err)); // "Error: Failed" (after 0.5 seconds)

Option 3: All no matter what with Promise.allSettled

In some situations, you may find Promise.all too strict, and want to handle the result of all promises even if one fails.

Promise.allSettled was created for this purpose.

It works in the same way as Promise.all, except Promise.allSettled will return the result of all promises, even if one rejects:

/* Promise.allSettled() continues handling promises even if one rejects */

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => { reject(new Error("Failed")); resolve("Fast") }, 500);
});
const promise2 = new Promise((resolve, reject) => {
  setTimeout(() => { resolve("Slow") }, 2000);
});
const promise3 = new Promise((resolve, reject) => {
  setTimeout(() => { resolve("Middling") }, 1000);
});

const myPromiseArray = [promise1, promise2, promise3];

Promise.all(myPromiseArray)
.then(res => console.log(res))
.catch(err => console.log(err));

// Output after 2 seconds:
// [
//     {
//         "status": "rejected",
//         "reason": "Failed"
//     },
//     {
//         "status": "fulfilled",
//         "value": "Slow"
//     },
//     {
//         "status": "fulfilled",
//         "value": "Middling"
//     }
// ]

Now, there is a difference in the output.

Rather than an array of return values, an array of objects is returned, containing information about whether each promise was fulfilled or reject and the return value for each.

To make this output a simple array, interpret .then only, pushing each result into a new array. For rejected promises, undefined will be the value in the array:

/* Arrayifying the result of Promise.allSettled() */

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => { reject(new Error("Failed")); resolve("Fast") }, 500);
});
const promise2 = new Promise((resolve, reject) => {
  setTimeout(() => { resolve("Slow") }, 2000);
});
const promise3 = new Promise((resolve, reject) => {
  setTimeout(() => { resolve("Middling") }, 1000);
});

const myPromiseArray = [promise1, promise2, promise3];

Promise.allSettled(myPromiseArray)
.then(res => { 
  let results = [];
  res.forEach(res => results.push(res.value));
  console.log(results); // [ undefined, "Slow", "Middling" ]
})

Summary

Promises provide you with several options for handling them in parallel. The option you choose should reflect the interconnectedness of the promises.

For promises that relate to independent tasks, simply handle each one individually. But if promises are interrelated, Promise.all and Promise.allSettled provide a better solution that takes into account this connectedness.

Use Promise.all for a ‘fail fast’ solution and Promise.allSettled to handle all results, regardless of outcome.

Related links