How to wait for multiple promises in JavaScript

May 10, 2023#javascript#how-to

In JavaScript, a Promise is an object that represents the eventual completion or failure of an asynchronous operation and its resulting value. A Promise can be in one of three states:

  • pending: the operation is still in progress and the promise’s value is not yet available.
  • fulfilled: the operation has completed successfully, and the promise’s value is now available.
  • rejected: the operation has failed, and an error message is available.

In modern web development, it is common to encounter situations where you need to make multiple asynchronous requests to an API or a database and wait for all of them to complete before taking further actions.

Using Promise.all

Promise.all is a static method of the Promise constructor that takes an iterable of promises as input and returns a single promise that resolves with an array of the fulfillment values of the input promises. The returned promise fulfills when all of the input’s promises fulfill, or rejects when any of the input’s promises rejects. Unlike Promise.allSettled, Promise.all does not wait for all of the promises to settle, but rejects immediately when any of them rejects.

To use Promise.all, you can pass an array of promises to it and then attach a .then() method to handle the resolved array or a .catch() method to handle the rejection reason. For example:

// An array of promises
let promises = [
  Promise.resolve(1),
  Promise.resolve(2),
  Promise.resolve(3),
];

// Pass the array to Promise.all
Promise.all(promises)
  .then((values) => {
    // values is an array of the resolved values
    console.log(values); // [1, 2, 3]
  })
  .catch((error) => {
    // This catch block will not be executed
    console.error(error);
  });

Using Promise.allSettled

Promise.allSettled is a static method of the Promise constructor that takes an iterable of promises as input and returns a single promise that resolves with an array of objects that describe the outcome of each promise. The returned promise fulfills when all of the input’s promises settle, meaning they are either fulfilled or rejected. Unlike Promise.all, Promise.allSettled does not reject immediately when any of the promises rejects, but waits for all of them to finish.

Each object in the array has a status property that can be either fulfilled or rejected, indicating the eventual state of the promise. If the status is fulfilled, the object also has a value property that holds the resolved value of the promise. If the status is rejected, the object also has a reason property that holds the rejection reason of the promise.

To use Promise.allSettled, you can pass an array of promises to it and then attach a .then() method to handle the resolved array. For example:

// An array of promises
let promises = [
  Promise.resolve(1),
  Promise.reject("error"),
  Promise.resolve(2),
];

// Pass the array to Promise.allSettled
Promise.allSettled(promises)
  .then((results) => {
    // results is an array of objects
    console.log(results);
    // [
    //   { status: "fulfilled", value: 1 },
    //   { status: "rejected", reason: "error" },
    //   { status: "fulfilled", value: 2 },
    // ]
  })
  .catch((error) => {
    // This catch block will not be executed
    console.error(error);
  });

Using async/await

You can use promises with async/await by declaring an async function and using the await keyword before a call to a function that returns a promise. This makes the code wait at that point until the promise is settled, meaning it is either fulfilled or rejected. When the promise is fulfilled, the value of the await expression becomes that of the fulfilled value of the promise. If the promise is rejected, the await expression throws the rejected value.

Using async/await can make your code more readable and concise, as you can write asynchronous code in a synchronous-like manner. You can also use try...catch blocks to handle errors from rejected promises. For example:

// Three promises
let promise1 = Promise.resolve(1);
let promise2 = Promise.resolve(2);
let promise3 = Promise.resolve(3);

// An async function that waits for the promises manually
async function useAwait() {
  try {
    // Use await for each promise and assign the result to a variable
    let result1 = await promise1;
    let result2 = await promise2;
    let result3 = await promise3;
    // Log the results
    console.log(result1); // 1
    console.log(result2); // 2
    console.log(result3); // 3
  } catch (error) {
    // Handle any error from the promises
    console.error(error);
  }
}

Another way to use async/await to wait for multiple promises is to use the await keyword inside a loop, such as a for loop or a for...of loop. This way, you can iterate over an array of promises and wait for each one individually. For example:

// An array of promises
let promises = [
  Promise.resolve(1),
  Promise.reject("error"),
  Promise.resolve(2),
];

// An async function that uses a for loop
async function useForLoop() {
  try {
    // Declare an empty array to store the results
    let results = [];
    // Use a for loop to iterate over the promises
    for (let i = 0; i < promises.length; i++) {
      // Use await to pause the execution until the current promise is resolved
      let result = await promises[i];
      // Push the result to the array
      results.push(result);
    }
    // Log the results
    console.log(results); // [1, 2]
  } catch (error) {
    // Handle any error from the promises
    console.error(error); // "error"
  }
}

Conclusion

Both Promise.all and Promise.allSettled allow you to execute multiple promises in parallel, meaning they are not dependent on each other and can run concurrently. However, Promise.all will fail fast if any of the promises rejects, while Promise.allSettled will wait for all of them to settle.

Depending on your use case, you may prefer one over the other. For example, if you need to perform multiple tasks that are required for your code to work correctly, you may want to use Promise.all and handle any error as soon as possible. If you need to perform multiple tasks that are independent and optional, you may want to use Promise.allSettled and handle each result individually.

You can use async/await to wait for promises in a sequential or serial way, meaning they are dependent on each other and can only run one after another. To do this, you can use the await keyword for each promise separately and assign the result to a variable. This way, the code will pause until the current promise is resolved before moving on to the next one.