New Promise functions: Promise.allSettled(ES11/2020) and Promise.any(ES12/2021)

2020年08月25日 2421Browse 0Like 0Comments

ECMAScript 2020, the 11th edition, introduces the Promise.allSettled, a new Promise combinator that does not short-circuit; And Promise.any is scheduled to be supported in ES2021 standard. The two new features are inroduced five years after Promise.all and Promise.race functions which were introduced in ES6(2015). The four combinator functions will make a more complete implementation of Promise for different asynchronous use cases.

Status of a Promise

A settled promise means that it is not pending, i.e. it is either fulfilled or rejected.

Promise.all

The Promise.all() method takes an iterable of promises as an input, and returns a single Promise that resolves to an array of the results of the input promises. This returned promise will resolve when all of the input's promises have resolved, or if the input iterable contains no promises. It rejects immediately upon any of the input promises rejecting or non-promises throwing an error, and will reject with this first rejection message / error.

Promise.all will shortcut on the first rejected promise, so we cannot get any resolved data/status once a promise is rejected even when some promises have already been resolved.

In order to get all the results(including status) of each promise after call Promise.all, we need to add a new function to return a new promise( promise.then() ), and return a new value with the status known in promise.then (either through the resolved branch or the rejected branch). Then iterate the results and filter the status after call Promise.all.

function reflect(promise) {
  return promise.then(
    (v) => {
      return { status: 'fulfilled', value: v };
    },
    (error) => {
      return { status: 'rejected', reason: error };
    }
  );
}
const promises = [ fetch('index.html'), fetch('https://does-not-exist/') ];
const results = await Promise.all(promises.map(reflect));

const successfulPromises = results.filter(p => p.status === 'fulfilled');

Promise.allSettled

const promises = [ fetch('index.html'), fetch('https://does-not-exist/') ];
const results = await Promise.allSettled(promises);
const successfulPromises = results.filter(p => p.status === 'fulfilled');

The similarities and diffrences with Promise.all:

  • They both receive an array of promises, and return a new promise;
  • Promise.allSettled will not short-circuit on any rejected promise, it will return a promise that is fulfilled with an array of promise state snapshots, but only after all the original promises have settled(resolved or rejected); while Promise.all will short-circuit on the first rejected promise.

Obviously, Promise.allSettled has some advantage as it can get all the state of all the settled promises.

const failedCount = results
  .filter(p => p.status === 'rejected').length

if(failedCount > 0) {
	const errors = results
	  .filter(p => p.status === 'rejected')
	  .map(p => p.reason);
 }

The compatibility is good among mianstream browsers(only not fully supported on IE, firefox on Android and Samsung Internet).


Promise.any

The any function returns a promise that is fulfilled by the first given promise to be fulfilled, or rejected with an AggregateError holding the rejection reasons if all of the given promises are rejected.

The similarities and diffrences with Promise.all:

  • They both receive an array of promises, and return a new promise; they both have short-circuit behavior as well;
  • They are fullfiled or rejected on opposite situations:
    • Promise.any will short-circuit on the first resolved promise, while Promise.all will short-circuit on the first rejected promise;
    • Promise.any will be rejected when all the promises are rejected, while Promise.all will be fullfiled when all promises are resolved.

const promises = [
  fetch('/endpoint-a').then(() => 'a'),
  fetch('/endpoint-b').then(() => 'b'),
  fetch('/endpoint-c').then(() => 'c'),
];

try {
  const first = await Promise.any(promises);
  // Any of the promises was fulfilled.
  console.log(first);
  // → e.g. 'b'
} catch (error) {
  // All of the promises were rejected.
  console.assert(error instanceof AggregateError);
  // Log the rejection values:
  console.log(error.errors);
}

Promise.any is in the proposal stage 4 - the finished state in this July; and the Google v8(v8.5) engine has released support to this feature, it will be supported in the new Chrome version(v85).

However, the compatibility of browsers is cutrently not as good as Promose.allSettled. We are expected to use this feature in 2021 or later.


Promise.race

The race function 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.

// example from mdn
const promise1 = new Promise((resolve, reject) => {
  setTimeout(resolve, 500, 'one');
});

const promise2 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'two');
});

Promise.race([promise1, promise2]).then((value) => {
  console.log(value);
  // Both resolve, but promise2 is faster
});
// expected output: "two"

Wrap-up

The table below shows the mian difference of these four main combinators in the Promise landscape.

function name description ES Version
Promise.race short-circuits when an input value is settled ES 2015
Promise.all short-circuits when an input value is rejected ES 2015
Promise.any short-circuits when an input value is fullfiled ES 2021
Promise.allSettled does not short-circuit ES 2020

Reference

Sunflower

Stay hungry stay foolish

Comments