Promises are simple in concept, but throughout this series, I’ve talked about a number of ways that they can become difficult to manage. This complexity is primarily due to the nature of asynchronous coding, which can often be unnatural and counter-intuitive. Promises are plumbing that helps capture data from asynchronous events, and it can often seem chaotic.
There are instances, however, where using promises actually makes your code easier to understand. An example of this is fetching a large amount of text (such as a book) and presenting that text to a user. The easiest approach is to download all of the text and then present it in one page.
This unfortunately means that the user has to wait for one large single download before the user will see anything. If the text is really large, this could mean many seconds or even minutes. A better approach is to break the text up on the backend and then download chapters individually in parallel, adding them to an array that will be used to render the book:
Promises make this pretty simple. There’s a problem, however. This code expects the chapters to end up in the array in the order that I want, but if chapter 2 is bigger than chapter 3, there is a chance that chapter 3 will finish first and the chapters will be out of order. Clearly, this is not what we want.
If we don’t care about immediately showing the user something, we can use a special promise created by a method called Promise.all(). This method takes in an iterable (such as an array) containing promises and resolves when all of the promises in that iterable have resolved. The fulfillment value of the promise returned by all is an iterable collection of the fulfillment values of each of the promises passed in. The order of the values matches the order of the input promises.
This means that we can change our example to this:
Because the all promise preserves the order of the promises, we can be assured that the chapters will appear in the correct order when the book is rendered. This also had the benefit of making our code leaner.
Let’s change the premise of this example a little. Imagine instead of downloading a Book, I want to download a plugin that I’m going to use as part of my application. Unfortunately, the download sites for the plugin get overloaded a fair amount, and so I might have to try a couple different mirrors before I get one that responds back quickly:
This could work, but it is highly inefficient — I’m trying all the mirrors in parallel and setting the plugin code if it hasn’t already been set (and I’m not really atomically checking and setting it, so it is possible it gets set twice).
Fortunately, we have a method called Promise.race that will do what we want in a much better way:
Just like with the all method, race has made this method considerably easier. The work of capturing whomever returns first is taken care of and all I have to do is collect the result from whichever one answered first.
Promise.any and Promise.allSettled
There are a couple of proposed extensions to the existing special Promises, namely any and allSettled. The difference between these and the ones I’ve already talked about is mostly in capturing all the output versus capturing the most relevant output.
Note that these features are still experimental and not supported by all browsers.
For example, when we used all above, we expect that at the end we have all of the chapters loaded into the document. What happens if fetching one of the chapters fails? The answer is that all rejects all the promises as soon as any of the promises passed in the iterable reject. The reject value of the first promise that is rejected can be caught:
If you want to get all of the chapters you can even if one should fail, use allSettled instead. It will return an array with all the results so that you can examine all of them:
The race method as we saw above returns the one promise that either fulfills or rejects first:
The any method, on the other hand, still returns the value from the first promise to fulfill, but if all the promises reject, you will get all of the rejected values:
In this article, I talked about some special promises that let you work with many promises at once, capturing all of the results or just the fastest one, depending on your needs. You can get just the first rejected reason or all of them, depending on whether you expect to all the promises to fulfill or at least one of them to fulfill. These special promises make this asynchronous code much easier to write.
That’s it for this series on promises. My next series will explore React hooks, which are the tools to replace redux and turn your complex class-based components into simpler functional components. Stay tuned.