Recent Advances and Improvements to JavaScript Promises
They’ve come a long way since the days of Promise.resolve() and Promise.reject().
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.
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()
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()
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()
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()
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
- ECMAScript 6 Language Specs
- ECMAScript 2021 Language Specs
- MDN docs for Promise.all
- MDN docs for Promise.allSettled
- MDN docs for Promise.any
- MDN docs for Promise.race