Recent Advances and Improvements to JavaScript Promises

They’ve come a long way since the days of Promise.resolve() and Promise.reject().

Paige Niedringhaus
Bits and Pieces

--

Photo by Crew on Unsplash

Promises, Promises.

JavaScript’s been slowly but steadily improving as a programming language ever since its inception back in 1995, and lately it seems as though those improvements are coming at a quicker and quicker pace. One of the biggest advancements (in my opinion, at least) was with the introduction of promises for asynchronous operations back in 2012.

The Promise object represents the eventual completion (or failure) of an asynchronous operation, and its resulting value, and it delivered JavaScript developers everywhere from the conundrum known as “callback hell.”

But that’s in the past, what I want to talk about now is the future: the future of promises. Not content to rest on the laurels of Promise.resolve() and Promise.reject(), the authors of JavaScript (the ECMAScript Committee) have added several new methods in both ES6 and ES2020 to make promises more useful and versatile than ever before.

I want to share some of the newest promise methods available to developers today, and make suggestions for when you might take advantage of them in your own development. Let’s get going.

Tip: Share your reusable components between projects using Bit (Github). Bit makes it simple to share, document, and organize independent components from any project.

Use it to maximize code reuse, collaborate on independent components, and build apps that scale.

Bit supports Node, TypeScript, React, Vue, Angular, and more.

Example: exploring reusable React components shared on Bit.dev

ES6 and ES2020 Really Took Promises to a Whole New Level of Usefulness

First with the release of the ECMAScript 6 Language Specification and then followed up by 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().

Promise.all()

The Promise.all() method (around since ES6) 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…

Promise.allSettled()

What you’re looking for if you just need to know when multiple promises have finished, regardless of their outcome, is Promise.allSettled(), which is brand new to ES2020. 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().

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()

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.

Conclusion

JavaScript has certainly come along way from its humble beginnings way back in 1995. In my opinion, one of the biggest improvements made to the language over the years was the introduction of promises to make managing asynchronous operations easier.

But the ECMAScript committee didn’t stop once the first promise-based methods were introduced. They recognized that things like multiple asynchronous operations may need to happen, and so with ES6 and ES2020 came new promise methods like Promise.allSettled(), Promise.any() and more. Although these may not always be the right solution for the job, knowing they’re available may just make your job as a web developer easier, at least, I hope so.

Check back in a few weeks — I’ll be writing more about JavaScript, React, ES6, or something else related to web development.

Thanks for reading. Promises take a minute to master, but they make asynchronous operations a whole lot easier, and I hope these new promise methods will be useful as you add new features and functionality to your own web apps. They certainly have been for me and my team. 🙂

Learn More

References & Further Resources

--

--

Staff Software Engineer at Blues, previously a digital marketer. Technical writer & speaker. Co-host of Front-end Fire & LogRocket podcasts