1×
Become a member
to unlock all features

Level Up!

Access all courses & lessons on egghead today and lock-in your price for life.

Autoplay

    Wait for the Fastest JavaScript Promise to Be Fulfilled with Promise.any()

    Marius SchulzMarius Schulz
    javascriptJavaScript

    The Promise.any() method accepts an array (or any other iterable) of promises as a parameter. It returns a Promise object that is fulfilled with the value of the first input promise to fulfill:

    • As soon as any input promise is fulfilled, the returned Promise object is fulfilled with that value.
    • If all input promises are rejected, the returned Promise object is rejected with an AggregateError which has an errors property containing an array of all rejection reasons.

    Promise.any() can be used to race multiple promises against each other and find the first promise to be fulfilled.

    Please note that at the moment, the Promise.any() method is only implemented in Firefox Nightly. Make sure to use a polyfill in your application to make it work across browsers.

    Code

    Code

    Become a Member to view code

    You must be a Member to view code

    Access all courses and lessons, track your progress, gain confidence and expertise.

    Become a Member
    and unlock code for this lesson
    Discuss

    Discuss

    Transcript

    Transcript

    Marius Schulz: 0:00 Once again, we are working with a list of Star Wars films. This is the demo application you've seen in previous lessons. We're still using the query API function to talk to the Star Wars API.

    0:12 When I go to the browser and refresh the page, we're fetching a list of films from starwars.egghead.training. Now, a real world API might be unavailable every now and then for various technical reasons. Let's see how our application behaves if we cannot reach egghead.training.

    0:29 I'm using Firefox here, so I'm going to open the Firefox developer tools. I'm going to head over to the network tab and I'm going to click those little cancel icon right here that says request blocking.

    0:40 Firefox lets us block any request whose URL matches a specific string. I'm going to block our requests to egghead.training to simulate that the Star Wars API is unavailable. When I refresh the page again, you can see that our API request is now unsuccessful and that we don't see any films anymore. This is because the Star Wars API is a single point of failure for our application. If it's unavailable, we cannot show any data.

    1:07 I want to use the Promise.any method to make our application more resilient against occasional API downtimes. So let's take a look at how Promise.any works. Let's go back to the console and let's also detach this window just so we have a little more space available. I'm also going to hide the warnings and open the multiline editor.

    1:27 Let me go ahead and create a bunch of test promises for us. I want several promises that have been rejected and one that has been fulfilled.

    1:37 Now let's pass those promises to the Promise.any method. Promise.any accepts a bunch of input promises and returns us a new promise. As the method name suggests, that new promise is fulfilled if any of the input promises is fulfilled. It is only rejected if all of the input promises are rejected and not a single one is fulfilled.

    1:58 Promise.any is useful in scenarios where we only need one of the input promises to succeed, rather than all of them. Let's go ahead and create a promise chain. I'm going to add a fulfillment handler as well as a rejection handler.

    2:19 All right, let's run this code and see what happens.

    2:23 We can see that our promise was fulfilled and this is because out of the three promises that we were passing to Promise.any, one of them was ultimately fulfilled with a value 42, so that's the value that we're getting here. Notice that if there's more than one promise that is fulfilled, we still only get back the first fulfillment value.

    2:44 The other fulfillment values are simply ignored.

    2:48 Let's now shift gears and take a look at what happens if all input promises are being rejected. I'm going to remove the last two promises from the array and I'm going to rerun our example.

    2:58 This time, our rejection handler is executed and it receives an aggregate error as its parameter. The aggregate error contains the rejection reasons of all of our input promises. We can access them using the errors property.

    3:13 Let's clear the output and run this again. This time, we can see an array of errors printed to the console. These are all the rejection reasons in the same order that we specified our promises in.

    3:25 Now that you know how the Promise.any method works, let's go back to our application and use it in a practical way. We want to avoid the single point of failure of the Star Wars API. I'm going to rename this constant to apiUrl1. I'm going to add a URL to another API that provides the same data.

    3:48 Let's go ahead and refactor our query API function. We are currently hard coding apiUrl1. Let's create a new function called query that accepts a root URL and an endpoint. Let's take our fetching logic and move it inside that function. Instead of apiUrl1, we are now going to use root URL.

    4:10 Back in our query API function, we now want to use Promise.any. We want to call our query function twice, once with apiUrl1 and our endpoint and once with apiUrl2. This is the moment of truth. Let's go back to our browser and give the page a refresh and there we go. We see our list of films again.

    4:35 If we go back to the network tab, we can see that the request to egghead.training is still failing. This is because our request blocking is still on. Whatever request we want to make, we now make it against two APIs.

    4:47 As long as one of the responses comes back successfully, we're good. Now, of course, if both APIs are unavailable at the same time, we're back to square one.

    4:57 We can simulate this by blocking requests to the other API as well. If we give this a refresh, you'll see that we now see an error message again.

    5:06 Let's now switch back to the console tab and take a look at the error message that we logged to the console. All of the promises that we passed through Promise.any ended up being rejected. This is why we're getting an aggregate error.

    5:19 However, "No promise in Promise.any was resolved," might not be the error message that we want to have. Let's attach a rejection handler and change the error to a better one.

    5:30 I'm going to return a promise that has been rejected with a custom error message. If we refresh the page one more time, we now get a better message log to the console.

    5:43 Now, here's the cool thing. Let me go ahead and collapse our code, and let's take a look at our promise chain. We haven't changed this piece of code at all. Every change we've made has been within the Create API function and the setup code.

    5:57 This means that callers of the Create API function don't even have to know that we're not talking to two APIs, rather than just a single one. All of this logic has been encapsulated and is fully opaque to the caller. Finally, we might have even made our application a little bit faster.

    6:14 Let's go back to the network tab, and let's just say the request blocking. Let me go ahead and give this page a refresh. You can see that the first response took 675 milliseconds, whereas the second one took 901. That's perfectly normal because response times vary. Let's refresh the page again.

    6:36 This time the second response came back much more quickly than the first one. Previously, we would have had to wait 646 milliseconds for a response, whereas now, we already get one after 356 milliseconds. This is a nice speedup.

    6:51 Obviously, it only works if the two APIs return the same data. Otherwise, we would have just introduced the race condition to our application.

    7:00 There you go. Use the Promise.any method when you have a bunch of promises, and you're only interested in the first fulfillment value rather than all of them.