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 resolved Promise, the .then() method will call the console.log() method.
  • If the getData() function returns a rejected Promise, the .catch() method will call the console.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.