The await
operator is used to wait for a promise to settle. It pauses the execution of an async
function until the promise is either fulfilled or rejected.
To learn more about async
and await
, check out my Asynchronous JavaScript with async/await course.
Instructor: [00:00] Here, we have the Query API function from the previous lesson again. In this lesson, I want to show you how to use async functions and the await operator to interact with promises. I'm going to start by creating a function called main and I'm going to invoke it right away.
[00:16] I'm going to call Query API and pass out the films endpoint. Now, what we get back from this function call is our films prompts. I'm going to log it to the console, just so we can convince ourselves this is actually a promise. Let's refresh the page, and sure enough we can see that we do get back a promise.
[00:36] Here is where it gets interesting. I'm going to make this an asynchronous function by adding the async keyword to the function declaration. Within async functions, we can use the await operator to wait for any promise to settle.
[00:49] The await operator will pass the function execution until this promise has been settled. When this promise is fulfilled, the await expression takes on the value that the promise was resolved with. In our case, that's the JSON that we got back from the API.
[01:03] We're going to look at what happens with rejected promises in a minute. For now, let's go ahead and rename our variable to be just films, because we no longer get back a promise. We get back the films themselves.
[01:16] I'm going to read out the page and you can see our six films. Let's show the number of films in the UI. Let's also get rid of this spinner once we're done. Refresh this again.
[01:37] Now, we see six films. Let's now take a look at what happens when this promise is rejected. I'm going to try to load an endpoint that doesn't exist. As you can see, we get a four of four and an uncalled promise error.
[01:55] When the await operator is applied through a rejected promise, it throws an error. Now, when there is an error within an asynchronous function, the asynchronous function implicitly returns a rejected promise. This means that we now have a rejected promise here.
[02:09] We didn't attach any rejection handlers. This is why it's an uncalled promise error. We can fix this by adding a rejection handler here. Let's log the error to the console. Let's show a very helpful error message to the user. Let's also remove the spinner.
[02:33] While this approach works, it is not a great solution. We now have logic in two places. We also have duplicate code. Luckily, we can use a cleaner error handling strategy with the await operator and that is a plain old try catch statement.
[02:50] I'm going to wrap this in a try block and I'm going to add our error handling logic in a catch block. I'm also going to add a finally block, so that we can make sure that we always remove this spinner, even if there was an error in the catch block.
[03:04] Let's run this again. We still see the error messages. Let's now run the happy path, and that looks good as well. Notice that we've written asynchronous code that looks fairly synchronous. You can read the entire main function line by line top to bottom, and you get the control flow you'd probably expect. This is one of the big benefits of using async await.
[03:32] Let's say we also want to show the number of planets like we did in the previous lesson. We're going to make another request to the API, but this time we're going to load the planets endpoint.
[03:49] Reload the page and we see 60 planets. However, if we take a look at the network tab, we can see that we're making both API requests sequentially rather than in parallel. This is because we're only kicking off the second request after the first promise has already been fulfilled.
[04:07] This is the perfect use case for the promise.all method. We want to make both requests at the same time and wait for both responses to come back. We can then await the promise return by promise.all, and we can also immediately destructure the results into the local variables, films and planets.
[04:33] Perfect. We now make both requests in parallel. I really like the combination of destructuring the await operator and the promise.all method. With very little syntax, we can add a third request here.
[04:54] Let's refresh this once more. We now see three API requests that are in-flight at the same time. To show you one more example of how the await operator can be used, I'm going to refactor the Query API function into an asynchronous function as well.
[05:10] First, we're going to make this an async function by adding the async keyword. Second, we're going to get rid of this callback and instead, we're going to await the promise that is returned by fetch.
[05:26] Finally, I'm going to convert this conditional expression into a proper if statement. If the response is OK, we want to deserialize the body as JSON and return the promise. Otherwise, we want to throw an error. Notice that this is looking simpler than before as well.
[05:46] I find this logic easier to understand. Let's give this one last refresh, and there you go. Async await working seamlessly with promises.
[05:58] If you want to learn more about async await check out my asynchronous JavaScript with async await course. It talks about asynchronous functions and the await operator in much more detail.
@gadi246: The right tool for the right job! async
& await
solve a different problem than redux-saga.
Thank you very much for this course. Really liked it.
The only question I have is why in the end we can simply return response.json()
. From the beginning I've understood that it is also an asynchronous function. Shouldn't we also await for it?
@Sofia: Great question! You can do either return await response.json();
or return response.json();
in this case. The latter works because the return value of an async
function is implicitly wrapped in a Promise, just as if you had manually wrapped Promise.resolve()
around the return value.
In the case of return response.json();
, we're passing a Promise to that Promise.resolve()
call. This doesn't result in a nested Promise, though; instead, the returned Promise adopts the state of the response.json()
Promise. We can therefore leave out the await
in this specific case.
Note that in some cases, the behavior might be different if you await
the Promise within the async
function before returning it. If the Promise is rejected and the await
expression is placed within a try
/catch
statement, the catch
branch is executed. That wouldn't be the case if you returned the Promise directly:
async function queryAPI(endpoint) {
const response = await fetch(API_URL + endpoint);
if (response.ok) {
try {
return /* await */ response.json();
} catch (e) {
// If the response.json() method doesn't synchronously
// throw an error, we won't enter this catch branch.
// Uncomment the `await` operator above and we will!
return Promise.reject(new Error("..."));
}
}
throw Error("Unsuccessful response");
}
I hope this illustrates the difference!
Very illuminating course. Really well-presented. Thank you.
Wow I learnt a ton... thanks
Excellent! Thanks very much! Unfortunately, the course ends where it starts to get interesting. The following topics are completely missing:
Amazing course!, Thank you very much! I was wondering what do you think about async/await vs generators and redux-saga?