Recent Advances and Improvements to JavaScript Promises

With the release of the ECMAScript 2021 Language Specification came some marked upgrades to the humble promise, and I’m really excited to share four of them with you, beginning with Promise.all().

The Promise.all() method takes an iterable (a list) 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 any of the input promises rejects and throws an error.

This method is especially useful when you have multiple asynchronous tasks that you need to wait on to resolve successfully before moving on to the next step in a program.

My team has used Promise.all() fairly often in our own codebase when we need to call particular REST endpoints but with different inputs: for instance getting product data for several items by passing the product IDs to the endpoint one at a time, then collecting all the results before moving on to displaying them to the user in the browser.

Promise.all()

Here’s an example of Promise.all() returning product details for these clothes.

Since I don’t have an actual API endpoint to query for the example above, I’ve just made each product object successfully resolve with a different clothing item: T-shirt, Jeans, Sweater.

I add all three of those objects to the array productList, and then I pass that array to the getProductDetails function. Once the function gets called, the variable productInfo waits until all three of the products in productList have resolved successfully before returning them (and logging the products to the console in this case).

If any one of the promises were to fail, promise.all() would immediately throw an error and exit, so this type of method is good when you need to be certain all your promises fulfilled successfully.

Pretty easy, right? Trust me, this functionality has come in really handy for us when we need to collect lots of asynchronous data, and I’m sure you’ll find it useful too.

Ok, so that’s good when you need all the data to resolve successfully, but what if it doesn’t matter whether the promises resolve or reject — they just need to finish? I’m glad you asked…

What you’re looking for if you just need to know when multiple promises have finished, regardless of their outcome, is Promise.allSettled(). Once again, this method takes in an array and returns an array of objects that describes the outcome of each promise.

When you have multiple asynchronous tasks that do not depend on each other, this is the type of promise method you could reach for. I used this method when I needed to fire off two independent API calls to generate two separate downloadable reports. Neither promise depended on the other succeeding, they just happened to both be initiated with a single button click by the user. And once both promises had come back (for better or worse), I could then inform the user of any reports available for them to view.

Promise.allSettled()

Promise.allSettled also returns an array, but it also shows the status of all promises, instead of terminating immediately if one promise rejects.

For this code snippet, I pass a list of report objects to the generateReports() function. Then, once more, the reportData variable waits for each promise to resolve or reject before returning the data in a new list.

The big difference is that each input returns an object describing the status of the promise as well as the value if the promise is fulfilled or the reason if the promise is rejected. The value (or reason) reflects what value each promise was fulfilled (or rejected) with.

Use cases for this particular promise method probably aren’t as plentiful as they are for Promise.all() (I imagine normally, we want all asynchronous tasks to succeed, not fail), but in situations such as this, where one asynchronous task doesn’t affect the outcome of another, it can fit the bill.

Now let’s move on to when you just need one promise, any promise, in a list, to resolve. Doesn’t matter which one, you just need one to succeed. Why that’s another new method: Promise.any().

As the name suggests, Promise.any() takes an iterable of Promise objects and as soon as one promise fulfills successfully, it returns a single promise that resolves with the value from that promise.

Essentially, the function short-circuits after one promise fulfills and does not wait for any of the others to complete (or fail) before moving on. This method will ignore all rejected promises up until the first promise that fulfills, as well.

Promise.any()

Whichever promise fulfills fastest is the one whose value gets returned with Promise.any(): performance testing different endpoints might be a good use for this.

The function whichPromiseWins() takes in a list of promises, and whichever of them fulfills first (in this case it will be promise2), that value is the only one that will get returned.

I’m honestly not sure what the real-world use case is for Promise.any(); the authors who proposed this addition give an example of determining which REST endpoint amongst three options is fastest, but personally, I haven’t used it yet. If the situation calls for this sort of thing though, I’m sure you’ll know it.

Please note: as of the time I’m writing this article, the Promise.any() method is still in stage 4 (the last stage) of the TC39 process of adding new functionality to the general JavaScript language. It has limited browser support at this time, so I might hold off deploying it into a production environment for a little while longer.

Right, last new promise method to introduce: Promise.race().

Promise.race() is similar to Promise.any() in that even though an array of promise objects is passed to Promise.race(), as soon as any of the promises rejects or fulfills, that single value (or reason) is returned.

Unlike Promise.any(), Promise.race() doesn’t care whether the promises it’s executing fulfill or reject; whichever promise returns first, is the one that moves forward as a value or a reason in the program.

Below are a couple of examples of what this might look like.

Promise.race()

Promise.race() in action. As with a true race, the fast promise to return wins, regardless of if it’s fulfilled or rejected.

The first list of promises passed to the promiseRaces() function, should result in the p3 rejection being printed out, as p3 has the shortest setTimeout period of all the Promise objects passed in. This is not a problem for Promise.race(), it just takes the rejection in the catch() block of the promiseRaces() function and logs out the reason.

With the second array of promises passed to promiseRaces(), the variable p6 should fulfill first. Once p6 resolves, Promise.race() will short-circuit and return the value of p6 and the program will continue.

I think that the use cases for this method, like Promise.any(), are less frequent, but I know they’re out there. And when they are, Promise.race() will be there to handle it.

Author: admin

Leave a Reply

Your email address will not be published.