The Promise.all()
method accepts an array (or any other iterable) of promises as a parameter. It returns a Promise
object that is fulfilled if all of the input promises are fulfilled or rejected if any of the input promises is rejected:
Promise
object is fulfilled with an array of fulfillment values of all promises (in the same order as the promises passed to Promise.all()
).Promise
object is rejected with that reason.Instructor: [00:00] For this lesson, I've created a helper function called Query API that takes an endpoint as a parameter and makes a GET request to this Star Wars API. Once the response is back, it inspects the OK property, and either deserializes the body as JSON or returns a rejected promise.
[00:17] We can use the Query API function to fetch the list of films like we did before. In this case, I just want to set the innerText of our output div to the number of films. We're going to say films.links, and then the word films. Let's see this in action. We see that we got six films back, but we also see that this spinner doesn't disappear.
[00:42] Let's use the finally method to hide the spinner once the promise has been settled. Try this again. Now, this spinner goes away. Let's say that we also want to show the number of planets in the films. We can go back in here and call the Query API function again, and this time load data from the planets endpoint.
[01:09] Then, once we have the response, we can move this in here and we can also display the number of planets. Let's make sure this works. Yes, it seems to work. However, notice that we see a blank screen after this spinner disappears. For half a second, we don't see anything.
[01:35] This is because our inner Query API call results in a dangling promise. This promise is not part of the outer promise chain. For this reason, we'll move this spinner to early and we see a blank screen. We can fix this by returning the inner promise from the fulfillment handler. This way, the spinner will only be removed once we got the planets back.
[01:59] However, we have another issue. Let's open the DevTools and take a look at the network tab. As you can see, we're making two sequential AJAX requests. This is because we're only kicking off the second API request after the response of the first one has come back.
[02:16] These two requests do not depend on each other. We could issue them in parallel. This is what the promise.all method comes into play. Promise.all takes multiple promises as a parameter and it returns a single promise that is fulfilled, when all of the input promises have been fulfilled.
[02:32] We can use promise.all to kick off both API requests at the same time. I'm going to paste those in here. Then, I'm going to create a promise chain using the return promise. This promise is fulfilled within array of fulfillment values of the input promises.
[02:49] We're going to find the fulfillment value of the first promise at index zero and the fulfillment value of the second promise at index one. I'm going to store these in variables called films and planets. Note that this order is exactly the same as the order that we specify when calling promise.all.
[03:05] Let's now move the rest of our code into the new promise chain. We can get rid of the old promise chain now. Let's see this in action, and that is looking good. Notice that we're no longer seeing the sequential waterfall. We're now making both requests in parallel, which means that our data is going to come back more quickly.
[03:29] Let's clean up our code a little bit. We can immediately destructure the results parameter into the variables films and planets. This way, we don't explicitly have to read the array indices zero and one. Let's make sure this still works.
[03:45] Yep, it's looking good. I want to stress again that this order must be exactly the same as the order of promises that we pass to promise.all. Otherwise, everything is going to be garbled up. Let's now see what happens, if one of the promises is rejected.
[04:00] I'm going to query an endpoint that doesn't exist. If I refresh the page now, we can see that this request fails. We get back a 404. Let's add to the console where we see that we have an uncalled promise rejection.
[04:14] This is because we haven't detached any rejection handlers to our promise chain. Let's go back here and let's add a rejection handler using the catch method. We're going to log the error to the console just like we did before.
[04:26] We're also going to show a user friendly error text. Try this again. Now, we see that our error is being handled. The promise that is returned from promise.all is rejected, if any of the input promises is rejected. This means that in order for this promise to be fulfilled, all input promises have to be fulfilled. That is usually what we want.
[04:50] All right. Let's get this working again by using the correct films endpoint. Let's see that this works, and it does. Now, the nice thing about promise.all is that it accepts arbitrarily many promises. We could add another API request in here and fetch the same species as well.
[05:09] Down here, we're going to add another variable to the destructuring pattern. We're going to concatenate another string. Refresh once more. Now, we see that we apparently have 37 species in the Star Wars movies.
[05:25] If we take a look at the network tab, we can see that all three requests have been issued in parallel. This is much faster than making all three requests sequentially. There's one final refactoring that I'd like to make, and that is to get rid of the promise variable.
[05:39] We don't really need the variable, so we can take the promise.all call and inline it down here. Then, we can get rid of the variable. Let me refresh one last time. As you can see everything is still working.
Please correct the word - length.
queryAPI("films").then(films => { output.innerText = '${films.legnth} films' ; });