Back in the days, we used callbacks. Then, we were blessed with promises. The journey does not stop there, though. ES7 introduced us to a new way of handling asynchronous calls: async/await.
Feel free to read my other articles if you would like to know more about async in general, or callbacks and promises.
The purpose of async/await
I often see beginners try to do something like that:
1 2 3 4 5 6 7 8 9 |
function fetchUsers() { let users; fetch(`${apiUrl}/users`) .then(usersResponse => usersResponse.json()) .then(usersData => { users = usersData; }) console.log(users); // undefined } |
They are surprised, that the users are undefined at the end. At some point, I was guilty of that as well. This is because we are used to synchronous code and even with promises being a really elegant way of handling asynchronous calls, we still need to adjust our way of thinking.
But what if we could write asynchronous code that looks synchronous?
1 2 3 4 5 |
async function fetchUsers() { const usersResponse = await fetch(`${apiUrl}/users`); const usersData = await usersResponse.json(); console.log(usersData); } |
Here, a response from fetch is assigned to a variable, and the code below is actually waiting to be executed until the users are fetched. This way, the code looks synchronous but is in fact asynchronous.
Differences in implementation
Notice that I’ve marked the function as async. Otherwise, an error would have been thrown: if you want to use await, you need to wrap it a function marked with async. There seem to be some differences in implementation, because, in Chrome, you can use await in the global scope, and for example, in Firefox you can’t.
1 2 |
const usersResponse = await fetch(`${apiUrl}/users`); // SyntaxError: await is only valid in async functions and async generators |
Therefore I wouldn’t rely on it and use it only in functions marked as async. An example of resolving this issue is using IIFE:
1 2 3 |
(async function() { const usersResponse = await fetch(`${apiUrl}/users`); })(); |
Error handling
Remember how in the previous article I’ve said that you should always handle your errors using a catch method? With async/await, a try catch block makes a big return!
1 2 3 4 5 6 7 8 |
async function fetchUsers() { try { const usersResponse = await fetch(`${apiUrl}/users`); const usersData = await usersResponse.json(); } catch (e) { console.error(e); } } |
This way, if anything goes wrong in the try block, the error is caught in the catch block.
Async / Await is not always a straight answer
1 2 3 4 5 6 7 8 9 10 |
async function fetchData() { try { const users = await fetch(`${apiUrl}/users`); const companies = await fetch(`${apiUrl}/companies`); const articles = await fetch(`${apiUrl}/articles`); console.log('users, companies and articles are fetched'); } catch (e) { console.error(e); } } |
There is a certain issue with the code above. Can you see that? Even though we would like users, companies and articles to be fetched in parallel, in this example, they are not. Companies are fetched only after the users were fetched. To resolve this issue, we actually need to use good old promises:
1 2 3 4 5 6 7 8 9 10 11 12 |
async function fetchData() { try { const data = await Promise.all([ fetch(`${apiUrl}/users`), fetch(`${apiUrl}/companies`), fetch(`${apiUrl}/articles`) ]); console.log('users, companies and articles are fetched in parallel'); } catch (e) { console.error(e); } } |
Since Promise.all runs all requests in parallel and returns a new promise to whom we can wait for, we gain a significant improvement in performance.
Looking under the hood
Wait… did I just write “good old promises”? To be exact, the async / await just operates on promises. Actually, await calls the then method.
1 2 3 4 5 6 |
const usersPromise = fetch(`${apiUrl}/users`); console.log(usersPromise.then) // a function here usersPromise.then(usersResponse => {}); const usersResponse = await usersPromise; |
In the example above, we get a promise from a fetch method, that we later can call a then method on. Await actually does the same thing. To get a better grasp of it, check this example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
function customThen(resolve, reject) { console.log('To be, or not to be'); resolve(this.response); } const dummyPromise = { response: 'that is the question', then: customThen }; (async function() { console.log(await dummyPromise); // To be, or not to be // that is the question })(); |
I created a dummyPromise here, that has a then method: this is all that await needs.
It also illustrates that await is calling dummyPromise.then() under the hood: you can see that this points to the promise itself, just like it would if a then method was simply called on it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
function customThen(resolve, reject) { console.log('To be, or not to be'); resolve(this.response); } const dummyPromise = { response: 'that is the question', then: customThen }; dummyPromise.then(response => { console.log(response); // To be, or not to be // that is the question }); |
Generators
To be even more specific, async / await is a syntactic sugar over generators. You can find a really good example on YDKJS.
In the next article, I will dig deeper into generators, and explain this example on the way, so stay tuned!
Summary
Async/await might be a great way to clean up your code. Watch out though, because if you use it without understanding how it works, you can hurt your performance. I hope that after reading this article, you are ready to go out there and code. Cheers!