Last week, we learned how to consume a promise in vanilla JS. We did this using the .then()
, .catch()
, and .finally()
methods introduced in ES2015 (ES6). Today, we're going to look at async functions using the async
and await
keywords, introduced in ES2017 (ES8). They let us write asynchronous code that feels more synchronous.
Let's revisit our getData()
function from last week. It mocks an API call and returns a Promise
with the result. The Promise
may be either resolved or rejected:
function getData() {
function fiftyFifty() {
return Math.round(Math.random());
}
function executor(resolutionFunc, rejectionFunc) {
setTimeout(function () {
// If there's an "error", reject the Promise with it
if (!fiftyFifty()) {
rejectionFunc(new Error("500 Interval Server Error"));
return;
}
// Otherwise, resolve the Promise with the "API data"
resolutionFunc([
{ id: 1, name: "SpongeBob" },
{ id: 2, name: "Patrick" },
{ id: 3, name: "Squidward" },
]);
}, 3000);
}
return new Promise(executor);
}
Promise chains
This is where we left off last week.
- If the
getData()
function returns a resolvedPromise
, the.then()
method will call theconsole.log()
method. - If the
getData()
function returns a rejectedPromise
, the.catch()
method will call theconsole.error()
method.
Either way, the .finally()
method will call the onSettled()
function.
function onSettled() {
console.info("The Promise returned by getData() is now settled.");
}
getData()
.then(console.log)
.catch(console.error)
.finally(onSettled);
Async functions
We can rewrite this using an async function and a try...catch...finally
statement:
async function main() {
try {
const data = await getData();
console.log(data);
} catch (error) {
console.error(error);
} finally {
console.info("The Promise returned by getData() is now settled.");
}
}
Now we just have to invoke our async function:
main();
Because we prepended our function declaration with the async
keyword, we can use the await
keyword within it. This also works with function expressions:
const main = async function () {
try {
const data = await getData();
console.log(data);
} catch (error) {
console.error(error);
} finally {
console.info("The Promise returned by getData() is now settled.");
}
};
The getData()
function returns a Promise
. The await
keyword waits for the Promise
to settle (become fulfilled or rejected) before moving onto the next line. As you can see, this lets us write asynchronous code in a way that feels more synchronous, making it easier to follow.
Note that async functions implicitly return a Promise
. In this example, the main()
function will return a Promise
that resolves with an undefined
value, since we didn't explicitly return anything. We can prove this by chaining a call to the .then()
method:
main().then(console.log); // undefined
Note, too, that the await
keyword can only be used within an async function, with the exception of JavaScript modules.
Finally, note that an async function doesn't have to be called main()
; this is an arbitrary name that I chose.