The Complete Guide to New Promise Methods: Promise.allSettled() and Promise.any()

Last Updated:
Image is “Threads of life” by Harish Rao, via Flickr; CC-BY-2.0

Since its introduction in ECMAScript 2015, the Promise object has provided two methods for tracking the state of asynchronous tasks: Promise.all() and Promise.race(). Although these methods have opened new possibilities in JavaScript, there are still use cases that aren’t covered. To fill this gap, two additional methods are proposed to be added to the specification: Promise.allSettled() and Promise.any().

Promise.allSettled() is currently a stage 4 proposal, which means its semantics, syntax, and API are completed. The good news is that all modern browsers, including Chrome 76+, Firefox 68+, and Safari 13+, have already implemented this feature. Promise.any(), on the other hand, is a relatively new proposal and is currently at stage 2. No browser natively supports this feature yet, so a polyfill is necessary if you want to use it today.

In this post, we’ll take a good look at new and existing promise methods (also known as promise combinators) and see how they differ.

Promise State Definitions

Before we delve into promise combinators, it’s important to have a clear understanding of promise states. A promise can be in one of the following three states:

  • Fulfilled: a promise is fulfilled if the final value has been successfully resolved and become available
  • Rejected: a promise is rejected if an error occurs and the final value cannot be determined
  • Pending: a promise that’s not fulfilled or rejected yet is pending

To describe a promise that’s either fulfilled or rejected, the term “settled” is often used. If you’re new to the concept of ECMAScript promises, you might need to have a look at the following resources before reading further:

Promise.race()

The Promise.race() method allows you to track multiple promises and react as soon as one of them fulfills or rejects. The syntax is as follows:

let promise = Promise.race( iterable );

Consider the following example:

const promise = Promise.race( [
    // fetch an image
    fetch( 'https://s.w.org/images/home/swag_col-1.jpg', { mode: 'no-cors' } ),
    // return a resolved promise
    Promise.resolve( 10 )
]);

promise.then( ( response ) => {
    console.log( response );
} ).catch( ( error ) => {
    console.log( error );
});

// logs:
// → 10

In this code, Promise.race() returns a pending Promise object that later fulfills with the value of the first promise in the iterable that fulfills. The fetch() method has to make an HTTP request, so Promise.resolve(10) is fulfilled faster.

A key difference between Promise.race() and Promise.all() is that the passed argument to the fulfillment handler is simply the value returned by the first promise that fulfills, instead of an array of fulfilled promises. Also notice that a second argument is passed to fetch(), which instructs it to fetch the resource with CORS disabled.

Passing a non-promise value will force the Promise.race() method to be immediately fulfilled with that value, as shown in this code:

const promise = Promise.race( [
    fetch( 'https://s.w.org/images/home/swag_col-1.jpg', { mode: 'no-cors' } ),
    'hello'
] );

promise.then( ( response ) => {
    console.log( response );
} ).catch( ( error ) => {
    console.log( error );
} );

// logs:
// → hello

If the first promise that settles is rejected, then Promise.race() will immediately reject:

const promise = Promise.race( [
    fetch( 'https://s.w.org/images/home/swag_col-1.jpg', { mode: 'no-cors' } ),
    Promise.reject( 'Error' )
] );

promise.then( ( response ) => {
    console.log( response );
} ).catch( ( error ) => {
    console.log( error );
} );

// logs:
// → Error

Here, the passed array to Promise.race() contains a promise that’s already rejected. So, the other promise is simply ignored, and Promise.race() rejects. But what if more than one promise is in the rejected state? In such a case, Promise.race() will use the value of the promise that comes first:

const promise = Promise.race( [
    Promise.reject( 'Error1' ),
    Promise.reject( 'Error2' ),
    Promise.reject( 'Error3' )
] );

promise.then( ( response ) => {
    console.log( response );
} ).catch( ( error ) => {
    console.log( error );
} );

// logs:
// → Error1

Promise.any()

The Promise.any() method takes an iterable of promises as an argument and returns a promise that will:

  • fulfill as soon as any of the promises in the given iterable fulfills, with the value of the fulfilled promise
  • reject if all of the promises are rejected, with an array of rejection reasons 

Note that at the time this writing no browser has implemented Promise.any() yet. So, to run the examples, you’ll need to use a polyfill such as this.

Let’s look at an example:

const promise = Promise.any( [
    Promise.reject( 'Error' ),
    fetch( 'https://s.w.org/images/home/swag_col-1.jpg?1' , { mode: 'no-cors' } ).then( () => 'w.org' ),
    fetch( 'https://en.wikipedia.org/static/images/project-logos/enwiki.png' , { mode: 'no-cors' } ).then( () => 'wikipedia.org' ),
    fetch( 'https://cdn-images-1.medium.com/fit/c/152/156/1*fXLN1zmmVsS2AO7uhKhntA.jpeg' , { mode: 'no-cors' } ).then( () => 'medium.com' )
] );

promise.then( ( fastest ) => {
    // the first promise that fulfills
    console.log( fastest );
} ).catch( ( error ) => {
    console.log( error );
} );

This code checks which image is loaded the fastest, and then logs the domain from which it was loaded. Unlike Promise.race() whose state is always determined by the first promise that settles, Promise.any() rejects only if all of the promises in the given iterable are rejected. In other words, Promise.any() is fulfilled with the value of the first fulfilled promise regardless of other rejected promises.

Let’s take a look at another example, but this time all of the promises are rejected:

const promise = Promise.any( [
    Promise.reject( 'Error1' ),
    Promise.reject( 'Error2' ),
    Promise.reject( 'Error3' )
] );

promise.then( ( response ) => {
    // ...
} ).catch( ( e ) => {
    console.log( e.errors );
} );

// logs:
// → ["Error1", "Error2", "Error3"]

In this example, the catch() method is executed instead of then(), with an array of errors passed to it as an argument. If you prefer, you can use the await keyword to handle the promise. Keep in mind that await must be used in an async function:

(async () => {
    try {
        result = await Promise.any( [
            Promise.reject( 'Error1' ),
            Promise.reject( 'Error2' ),
            Promise.resolve( 'Success!' )
        ] );

        console.log( result );
    } catch(e) {
        console.log(e);
    }
})();

// logs:
// → Success!

Promise.all()

The Promise.all() method accepts an iterable object as an argument and returns a single promise that’s fulfilled when the promises in the iterable are fulfilled. Here’s an example:

const promise = Promise.all( [
    // fetch two files in JSON format
    fetch( 'https://api.github.com/orgs/axios' ),
    fetch( 'https://api.github.com/users/sideshowbarker' )
] );

promise.then( ( response ) => {
    console.log( response[0].ok );
    console.log( response[1].ok );
} ).catch( ( error ) => {
    console.log( error );
} );

// logs:
// → true
// → true

Each fetch() method in this example takes a string as an argument that’s pointing to a JSON file. When fetch() is called, it returns a promise, which is fulfilled when the file is successfully loaded. Once all promises in the iterable are fulfilled, the promise returned by Promise.all() is fulfilled.

You can access the values of fulfilled promises via an array, which is passed as an argument to then(). The values will be available in the same order they were defined. In this case, we simply log the ok property of each element to check if the operations have been successful.

If any of the images fail to load, the promise returned by Promise.all() will immediately reject. Put another way, all of the promises in the iterable must fulfill, otherwise Promise.all() rejects:

const promise = Promise.all( [
    fetch( 'https://api.github.com/orgs/axios' ),
    Promise.reject( 'Error!' ),
    fetch( 'https://api.github.com/users/sideshowbarker' )
] );

promise.then( ( response ) => {
    console.log( response[0].ok );
    console.log( response[1].ok );
} ).catch( ( error ) => {
    console.log( error );
} );

// logs:
// → Error!

In this code, the second promise in the array rejects. As a result, Promise.all() rejects, and the catch() method is called to handle it.

Promise.allSettled()

While Promise.all() immediately rejects if one of the promises rejects, Promise.allSettled() resolves regardless of whether the given promises fulfill or reject. An example should make this clear:

const p1 = Promise.resolve( 'Success' );
const p2 = Promise.reject( 'Error!' );
const p3 = Promise.resolve( 123 );

Promise.allSettled( [p1, p2, p3] ).then( ( response ) => {
    response.forEach( result => console.log( result.value || result.reason ) )
} );

// logs:
// → Success
// → Error!
// → 123

As you can see, the outcome of all promises, whether rejected or fulfilled, is available as an array in the then() method. Inside the method, a forEach() loop is used to iterate over the elements of the array. If the current element in the loop has a value property, its value is logged to the console. If not, that means the promise has been rejected, and the value of the reason property is logged instead.

Previously, to achieve this task, JavaScript developers had to use a pattern like the one shown below. This pattern, which is still used to support older browsers, involves calling the map() method to create a new array of promises and then passing the array to Promise.all():

const p1 = Promise.resolve( 'Success' );
const p2 = Promise.reject( 'Error!' );
const p3 = Promise.resolve( 123 );

const arr = [p1, p2, p3].map( p => {
    // catch any rejected promise and return a promise again
    return p.catch( erorr => erorr )
});

Promise.all( arr ).then( response => response.forEach( result => console.log( result ) ) );

// logs:
// → Success
// → Error!
// → 123

Conclusion

Promise.all() and Promise.race() are welcome additions to JavaScript that allow further control over asynchronous operations. If you’ve ever worked with a promise library, chances are it already comes with similar functionality. But as JavaScript programmers move away from third-party libraries in favor of native implementations, such additions become more critical, especially since native APIs often offer better run-time performance.

JavaScript is constantly evolving and, besides promise combinators, many other new features are expected to be added to the language. So, be sure to check the list of finished proposals to get updated on what’s new.

 

Author: Faraz Kelhini

Faraz is a professional JavaScript developer who is passionate about moving the web forward and promoting patterns and ideas that will make web development more productive. He regularly writes about JavaScript, web development, and best practices on his blog and other popular blogs. You can follow him on Twitter @FarazKelhini.

We help JavaScript Developers Land Their Dream Job.

Current Open Roles

Your Princess (or Prince!) IS in this Castle – Node.js developer in Central London
London

Work with Node.JS and Ruby in the heart of Sydney
Sydney

Work with giant, caged robots, and Node.js; Senior Developer, West London
West London, UK

Want to help small family businesses thrive? Senior React/Redux Developer for a tech company enabling entrepreneurs
London

The only NodeJS job with a swimming pool; very exciting financial services company, City of London
London

Swap dreary grey skies for life in Bangkok - Senior Expat NodeJS Engineer in the Land of Smiles (offered in partnership with TechInParadise.com)
Bangkok

Sprechen Sie Deutsch und TypeScript fließend? Möchten Sie in London leben?
London

Senior React.js developer needed
London

Senior NodeJS Developer
London

Seasoned, highly-skilled React.js Developer looking for a satisfying challenge? Step right up.
London

Polygot JavaScript Developer with Go experience, and a desire to jump into other languages
London

Mobile App Developer Wanted for High-Growth Fundraising Platform
Kent

Make holidays happier: book yourself into this Senior Javascript Developer’s resort
London

JavaScript for High Performance; Senior Developer, London
London

It’s Not Rocket Science . . . Or is it?
London

Developer Wanted for High-Traffic Platform and (Disruptive?) Product
London

Can you help our client migrate to Node.js? Docklands, London
London

Big Developer Energy with Sassy Start-Up Vibe; Senior Node.js Developer in London
London

Aussie Start-up (Funded) Seeks Brilliant Developer
Sydney

And on the Seventh Day, You Rested
London

A JS job the banks don’t want you to have…
London