JonLuca's Blog

14 May 2020

Promise.all vs Promise.allSettled in JavaScript

I was reading the MDN docs on JavaScript promises and thought the difference between Promise.all and Promise.allSettled was interesting and slightly nuanced, and would be fun to discuss.

About Promises

From the docs linked above:

A Promise is a proxy for a value not necessarily known when the promise is created. It allows you to associate handlers with an asynchronous action’s eventual success value or failure reason.

A Promise is in one of these states:

> pending: initial state, neither fulfilled nor rejected.

> fulfilled: meaning that the operation completed successfully.

> rejected: meaning that the operation failed.

You can instantiate a promise like so:

const someFutureValue = new Promise((resolve, reject) => {
  setTimeout(resolve, 2000);
});

Difference between all and allSettled

tldr; Promise.all will immediately reject if any of the promises fail to resolve, wherease Promise.allSettled will await the completion of all promises.

Promise.all()

Promise.all() is passed an iterable (usually an array of other promises) and will attempt to resolve all of them. If any of these promises throws an exception or rejects, Promise.all will immediateley invoke its reject.

You should always surround Promise.all() with a try/catch or a .catch().

const promiseFunction = async () => {
  const resolvingPromise = new Promise((resolve) =>
    setTimeout(() => resolve("success"), 1000)
  );
  const rejectingPromise = new Promise((resolve, reject) =>
    setTimeout(() => reject("fail"), 500)
  );
  try {
    const allPromises = await Promise.all([resolvingPromise, rejectingPromise]);
  } catch (e) {
    // e is 'fail', which is  the contents of the reject function from rejectingPromise
    // resolvingPromise =  Promise {<pending>}
    console.log(e);
  }
};
promiseFunction();

In the above code snippet, when console.log(e) is called, the status of resolvingPromise will still be pending. The code invoked in the catch statement is run roughly 500ms after the promiseFunction invocation - since rejectingPromise rejects after 500ms.

const promiseFunction = async () => {
  const resolvingPromise = new Promise((resolve) =>
    setTimeout(() => resolve("success"), 1000)
  );
  const resolvingPromiseTwo = new Promise((resolve, reject) =>
    setTimeout(() => resolve("successTwo"), 500)
  );
  try {
    const allPromises = await Promise.all([
      resolvingPromise,
      resolvingPromiseTwo,
    ]);
    console.log(allPromises);
    // allPromises = ['success', 'successTwo']
  } catch (e) {
    // this code block is never executed
    console.log(e);
  }
};
promiseFunction();

The Promise.all resolves 1000ms after invocation - resolvingPromiseTwo resolves after 500ms, and resolvingPromise resolves after 1s, which completes all the promises, which allows the Promise.all promise to resolve. Promise.all() returns an array with the resolved value of each of the Promises passed to it, in the order in which they are passed. In the above case, it is an array of strings = ['success', 'successTwo'].

Promise.allSettled()

Promise.allSettled() is also passed an iterable (usually an array of other promises) and will attempt to resolve all of them. If any of these promises throws an exception or rejects, its status is set to rejected.

An important note is that Promise.allSettled can never reject. You do not need to wrap it with try/catch - it will always resolve.

const promiseFunction = async () => {
  const resolvingPromise = new Promise((resolve) =>
    setTimeout(() => resolve("success"), 1000)
  );
  const rejectingPromise = new Promise((resolve, reject) =>
    setTimeout(() => reject("fail"), 500)
  );
  try {
    const allPromises = await Promise.allSettled([
      resolvingPromise,
      rejectingPromise,
    ]);
    console.log(allPromises);
    // allPromises
    // [
    //   {status: "fulfilled", value: 'success'},
    //   {status: "rejected",  reason: 'fail'}
    // ]
  } catch (e) {
    // this code block is never executed
    console.log(e);
  }
};
promiseFunction();

Promise.allSettled resolves after 1s - it awaits all the promises, regardless of their status or if they reject. It then returns an array of objects. It’s up to the caller to check if each promise fulfilled or rejected. If it fulfilled, the value of the object will be contain what was resolved. If the status is rejected, it will contain a key called reason, which is what was thrown or rejected.

Bonus: Promise.race

There is also another interest function in Promise called Promise.race.

The Promise.race() method returns a promise that fulfills or rejects as soon as one of the promises in an iterable fulfills or rejects, with the value or reason from that promise. 1

const promiseFunction = async () => {
  const resolvingPromise = new Promise((resolve) =>
    setTimeout(() => resolve("success"), 1000)
  );
  const resolvingPromiseTwo = new Promise((resolve, reject) =>
    setTimeout(() => resolve("successTwo"), 500)
  );
  try {
    const racedPromises = await Promise.race([
      resolvingPromise,
      resolvingPromiseTwo,
    ]);
    console.log(racedPromises);
    // both promises would've resolved, but resolvingPromiseTwo was faster, so racedPromises = 'successTwo'`
  } catch (e) {
    // this code block is only executed if the first promise to settle rejects/throws
    console.log(e);
  }
};
promiseFunction();

In the above, console.log(racedPromises) is called 500ms after invocation - both promises would’ve resolved, but resolvingPromiseTwo was faster, and it is what Promise.race resolves with.

This is pretty specific behavior - most of the time this is probably not what you want, but it’s pretty neat!

Footnotes
  1. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race 

JonLuca

JonLuca at 12:50

To get notified when I publish a new essay, please subscribe here.