Execute Program

JavaScript Concurrency: Promise allSettled

Welcome to the Promise allSettled lesson!

This lesson is shown as static text below. However, it's designed to be used interactively. Click the button below to start!

  • Promise.all will reject as soon as any of the individual promises rejects. Sometimes we'd rather wait for all of the promises to finish, even if one or more of them rejects. For that, we can use Promise.allSettled.

  • This presents a problem: what value should Promise.allSettled fulfill with? With Promise.all, the answer is pretty straightforward. If all of its promises fulfill, Promise.all also fulfills with an array of the individual promises' values. If any of its promises reject, Promise.all immediately rejects with the rejected promise's reason.

  • For Promise.allSettled, the equivalent would be an array of the promises' fulfilled values and rejection reasons. But that wouldn't give us a way to know which promises were fulfilled and which were rejected!

  • We can see the problem by writing our own simplified implementation of allSettled. It uses Promise.all, but catches rejections before they get to Promise.all, which would stop execution. If a promise rejects, our function uses catch to return the reason for that promise. (Remember that catch turns a rejected promise into a fulfilled one.)

  • >
    function ourAllSettled(promises) {
    return Promise.all(promises.map(promise =>
    promise.catch(reason => reason)
    ));
    }

    ourAllSettled([
    Promise.resolve(5),
    Promise.reject('rejection reason'),
    ]);
    Asynchronous Icon Async Result:
  • The 5 here came from a fulfilled promise and the 'rejection reason' came from a rejection, but we can't tell which is which. We need some "out of band data" or "metadata" to indicate which promises fulfilled and which rejected.

  • We can wrap each of our promise results in an object with two properties. For fulfilled promises, the properties are status and value, since promises fulfill with values. For rejected promises, the properties are status and reason, since promises reject with reasons.

  • We'll use then to handle fulfilled promises and catch to handle rejected promises. Here's our updated function.

  • >
    function ourAllSettled(promises) {
    return Promise.all(promises.map(promise =>
    promise
    .then(value => {
    return {status: 'fulfilled', value: value};
    })
    .catch(reason => {
    return {status: 'rejected', reason: reason};
    })
    ));
    }
  • Now ourAllSettled returns an array of promise results, each with a clear status.

  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    ourAllSettled([
    Promise.resolve(5),
    Promise.reject('rejection reason'),
    ]);
    Asynchronous Icon Async Result:
  • ourAllSettled now works like the built-in Promise.allSettled function! Here's what running Promise.allSettled looks like in practice:

  • >
    Promise.allSettled([
    Promise.resolve(1)
    ]);
    Asynchronous Icon Async Result:
  • >
    Promise.allSettled([
    Promise.reject(new Error('oh no'))
    ]);
    Asynchronous Icon Async Result:
  • We've seen that Promise.all rejects as soon as one of the underlying promises rejects. Promise.allSettled is different: it never rejects. The individual promises may reject, which will be indicated by a status of 'rejected', but Promise.allSettled itself always fulfills with an array of objects as we saw above.

  • >
    // Remember that allSettled's promise statuses are "fulfilled" or "rejected".
    Promise.allSettled([
    Promise.resolve(1),
    Promise.resolve(2),
    ]).then(results =>
    results.map(
    result => result.status));
    Asynchronous Icon Async Result:
    {fulfilled: ['fulfilled', 'fulfilled']}Pass Icon
  • >
    // Remember that allSettled's promise statuses are "fulfilled" or "rejected".
    Promise.allSettled([
    Promise.resolve(1),
    Promise.reject(new Error('oh no')),
    ]).then(results =>
    results.map(
    result => result.status));
    Asynchronous Icon Async Result:
    {fulfilled: ['fulfilled', 'rejected']}Pass Icon
  • Here's a code problem:

    Finish the fulfilledValues function below. It takes a promises array as an argument. It should return an array of the fulfilled values from the promises. Rejected promises should be ignored, since they don't have a fulfilled value.

    Some hints:

    1. Use Promise.allSettled, which returns a promise containing an array of result objects.
    2. Then remove the rejected results with .filter(...).
    3. Then get the values out of the remaining results with .map(...).
    function fulfilledValues(promises) {
    return Promise.allSettled(promises).then(results => {
    return results
    .filter(result => result.status === 'fulfilled')
    .map(result => result.value);
    });
    }
    fulfilledValues([
    Promise.resolve('Amir'),
    Promise.reject(new Error("User doesn't exist")),
    Promise.resolve('Cindy'),
    ]);
    Asynchronous Icon Async Result:
    Goal:
    {fulfilled: ['Amir', 'Cindy']}
    Yours:
    {fulfilled: ['Amir', 'Cindy']}Pass Icon
  • The Promise.all and Promise.allSettled functions are used often in production web applications. You'll find them useful when you need to make multiple API requests, or write multiple changes to a database, or access multiple third-party web APIs quickly.

  • Of the two, Promise.all is more common because we usually want to fail immediately when something goes wrong. However, Promise.allSettled can be useful when the promises are independent, or we need to know the result of each promise.