Async/await: a modern syntax for asynchronous JavaScript
Async-await syntax was introduced to JavaScript in ES2017 as a way to handle JavaScript promises in a way that resembles synchronous code. Under the hood, as we will see in this tutorial, async-await is powered by promises.
It does so by introducing two new keywords: async
and await
.
To work with async-await
, it helps a lot to understand JavaScript promises.
Need a refresher? Then you may want to check out our guide to promises before reading on.
Async-await syntax
The async keyword
The async
keyword allows us to transform a regular function to one the returns a promise by default.
No writing of promises in necessary: just append the keyword async
before a function and it will wrap its contents within a promise.
For example, look how we can handle the result of myAsyncFunction
using promise syntax:
async function myAsyncFunction () { return 2+2; } myAsyncFunction().then(res => console.log(res)); // 4
This is the first feature of the async
keyword. It's second feature is that enables the use of the await
keyword within a function prepended by async
.
The await keyword
The await
keyword can be used within a function marked as async
. When prepended before a promise, it awaits its outcome.
For example:
const myPromise1 = new Promise(function(resolve, reject) { setTimeout(function(){ resolve("First task"); },2000); }); async function myAsyncFunction() { const result = await myPromise1; console.log(result); } myAsyncFunction();
Remove the await
keyword in front of myPromise1
and the console.log(result)
would return Promise { <pending> }
because the result was attempted to be accessed too soon.
It is important to know that the use of the await
keyword pauses further execution of the contents of the function until a process is complete.
This can be seen clearly by running the below example, in which we await one promise after another:
const myPromise1 = new Promise(function(resolve, reject) { setTimeout(function(){ resolve("First task"); },1000); }); const myPromise2 = new Promise(function(resolve, reject) { setTimeout(function(){ resolve("Second task"); },2000); }); const myPromise3 = new Promise(function(resolve, reject) { setTimeout(function(){ resolve("Third task"); },3000); }); async function myAsyncFunction() { console.log("Here we go!") const result1 = await myPromise1; console.log("First promise done"); const result2 = await myPromise2; console.log("Second one down"); const result3 = await myPromise3; console.log("Another one bites the dust!"); } myAsyncFunction(); // "Here we go!"..."First promise done"..."Second one down"..."Another one bites the dust!"
But this is not serial processing because JavaScript engine starts executing the contents of a promise immediately (unless it is wrapped in a function). So if all promises took the same amount of time to complete, all promises would return results at the same because there is nothing to wait for after the first promise is complete:
const myPromise1 = new Promise(function(resolve, reject) { setTimeout(function(){ resolve("First task"); },2000); }); const myPromise2 = new Promise(function(resolve, reject) { setTimeout(function(){ resolve("Second task"); },2000); }); const myPromise3 = new Promise(function(resolve, reject) { setTimeout(function(){ resolve("Third task"); },2000); }); async function myAsyncFunction() { console.log("Here we go!") const result1 = await myPromise1; console.log("First promise done"); const result2 = await myPromise2; console.log("Second one down"); const result3 = await myPromise3; console.log("Another one bites the dust!"); } myAsyncFunction(); // Immediately: "Here we go!" // After 2 seconds: "First promise done","Second one down","Another one bites the dust!"
await
keyword to be used outside of an asynchronous function at the top level (in the global scope).
Error-handling
To handle a potential error in an async function, we can use try...catch
syntax.
We first wrap our asynchronous function code in a try
statement. We then follow it with a catch
statement and do our error-handling there:
const myPromise1 = new Promise(function(resolve, reject) { setTimeout(function(){ resolve("First task"); },2000); }); const myPromise2 = new Promise(function(resolve, reject) { setTimeout(function(){ reject("Promise rejected!"); resolve("Second task"); },2000); }); const myPromise3 = new Promise(function(resolve, reject) { setTimeout(function(){ resolve("Third task"); },2000); }); async function myAsyncFunction() { try { const result1 = await myPromise1; const result2 = await myPromise2; const result3 = await myPromise3; } catch(err) { console.log("Houston, we have a problem: " + err); } } myAsyncFunction(); // Houston, we have a problem: Promise rejected!
The catch
statement works just like .catch
in a .then
chain: if there is an error in our asynchronous function statement, processing of the try
statement will stop and the catch
statement will be triggered.
An example using Fetch
Under the hood, async-await
works with promises. So any async-await
syntax can be rewritten with promises syntax and vice-versa.
Here is the way the result of a fetch request would normally be dealt with using ES6 promises syntax:
function withPromiseSyntax() { fetch('https://httpbin.org/get') .then(res => res.json()) .then(data => console.log(data)) .catch(error => console.log(error)) } withPromiseSyntax()
And now with async-await
:
async function withAsyncAwait() { try { const res = await fetch('https://httpbin.org/get'); const data = await res.json(); console.log(data); } catch(err) { console.log(err); } } withAsyncAwait();
Both code snippets achieve the same outcome. However, except for the async
and await
keywords, the async-await syntax strongly resembles synchronous code.
Async-await loop
Since ES2018 we can execute promises serially using an async...for
await loop.
Promises are loaded into anonymous functions (so they do not execute immediately) and stored in an array.
Now, a for...of
loop is created inside an async function. With the loop, each function in the array is called, prepended by the keyword await. This make the loop await the result of each function call before moving on to the next:
// Create an array of promises const myPromises = [ _ => new Promise(resolve => setTimeout(() => {return resolve("First task")}, 1000)), _ => new Promise(resolve => setTimeout(() => {return resolve("Second task")}, 1000)), _ => new Promise(resolve => setTimeout(() => {return resolve("Third task")}, 1000)), ]; // Create the async function async function executeMyPromises(myPromises) { for (let promise of myPromises) { console.log(await promise()); } }; // Run function executeMyPromises(myPromises); // "First task"..."Second task"..."Third task"
Combining async-await with Promise object methods
The async-await
syntax can be combined with methods available on the Promise
object to flexibly work the results of multiple promises.
For example, Promise.all
returns the results of all promises together when all are complete:
const myPromise1 = new Promise(function(resolve, reject) { setTimeout(function(){ resolve("First task"); },2000); }); const myPromise2 = new Promise(function(resolve, reject) { setTimeout(function(){ resolve("Second task"); },2000); }); const myPromise3 = new Promise(function(resolve, reject) { setTimeout(function(){ resolve("Third task"); },2000); }); async function myAsyncFunction() { try { const result = await Promise.all([ myPromise1, myPromise2, myPromise3]); console.log(result); } catch(err) { console.log("Houston, we have a problem: " + err); } } myAsyncFunction(); // Output after 2 seconds: // ["First task","Second task","Third task"]
It is possible to replace Promise.all
in the above code with Promise.allSettled
, Promise.any
or Promise.race
to process the results differently.
See our post on handling multiple promises
in JavaScript for a breakdown of these methods.
Summary
Async-await syntax takes JavaScript full circle: from synchronous code to callbacks, callbacks to promises and now, with async-await, our code can look and read like synchronous code again.
It is likely that future updates build upon – rather than revolutionize – this framework.
Related links
- W3Schools reference: JavaScript Async/Await
- DigitalOcean: Exploring Async/Await Functions in JavaScript
- OpenJS Foundation: Modern Asynchronous JavaScript with Async and Await