ECMAScript Proposal, specs, and reference implementation for Promise.allSettled
Switch branches/tags
Nothing to show
Clone or download
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.gitignore Draft of spec text (#14) Oct 18, 2018
README.md Link to TC39 meeting notes Nov 10, 2018
index.html Draft of spec text (#14) Oct 18, 2018
package.json Draft of spec text (#14) Oct 18, 2018
spec.html Draft of spec text (#14) Oct 18, 2018

README.md

Promise.allSettled

ECMAScript proposal and reference implementation for Promise.allSettled.

Author: Jason Williams (BBC)

Champion: Mathias Bynens (Google)

Stage: 1

Overview and motivation

A common use case that I and many others come across, is to want to settle all promises within an array. Due to the short circuit nature of Promise.all() any rejected promise will cancel the entire operation and return a rejection. The key feature of the .allSettled() method is that it allows us to settle all promises.

Promise.allSettled() returns a promise that is fulfilled with an array of promise state snapshots, but only after all the original promises have settled, i.e. become either fulfilled or rejected.

This method is used in its static form on arrays of promises, in order to execute a number of operations concurrently and be notified when they all finish, regardless of success or failure.

Why allSettled?

We say that a promise is settled if it is not pending, i.e. if it is either fulfilled or rejected.

Examples

Currently you would need to iterate through the array of promises and return a new value with the status known (either through the resolved branch or the rejected branch.

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');

The proposed API allows a developer to handle these cases without creating a reflect function and/or assigning intermediate results in temporary objects to map through:

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

Collecting errors example:

Here we are only interested in the promises which failed, and thus collect the reasons. allSettled allows us to do this quite easily.

const promises = [ fetch('index.html'), fetch('https://does-not-exist/') ];

const results = await Promise.allSettled(promises);
const errors = results
  .filter(p => p.status === 'rejected')
  .map(p => p.reason);

Real-world scenarios

A common operation is knowing when all requests have completed regardless of the state of each request. This allows developers to build with progressive enhancement in mind. Not all API responses will be mandatory.

Without Promise.allSettled this is tricker than it could be:

const urls = [ /* ... */ ];
const requests = urls.map(x => fetch(x)); // Imagine some of these will fail, and some will succeed.

// Short-circuits on first rejection, all other responses are lost
try {
  await Promise.all(requests);
  console.log('All requests have completed; now I can remove the loading indicator.');
} catch {
  console.log('At least one request has failed, but some of the requests still might not be finished! Oops.');
}

Using Promise.allSettled would be more suitable for the operation we wish to perform:

// We know all API calls have finished. We use finally but allSettled will never reject.
Promise.allSettled(requests).finally(() => {
  console.log('All requests are completed: either failed or succeeded, I don’t care');
  removeLoadingIndicator();
});

Userland implementations

Further reading

TC39 meeting notes

Specification

Implementations

  • none yet