Skip to main content Accessibility Feedback

async and await in JavaScript

This week, we learned about JavaScript Promises and the fetch() method. Today, we’re going to learn how async and await can make writing Promises easier.

Let’s dig in!

What are async and await?

The async and await operators allow you to treat asynchronous code like synchronous code.

For example, in the traditionalFn() function, we make an asynchronous API call with the window.fetch() method. When the response comes back and is parsed into JSON, we log it to the console. Immediately after making the call, we also log a message to the console.

function traditionalFn () {
	fetch('https://jsonplaceholder.typicode.com/posts/').then(function (response) {
		return response.json();
	}).then(function (data) {
		console.log('Traditional Fetch', data);
	});
	console.log('Traditional Message');
}
traditionalFn();

When we run the traditionalFn() function, Traditional Message is logged into the console before Traditional Fetch and the data are.

Because fetch() is asynchronous, the rest of our function does not wait for it to complete before continuing. But we if we wanted to do just that?

How to make asynchronous code wait before continuing

When you use the async operator before a function, you turn it into an async function.

Inside an async function, you can use the await operator before asynchronous code to tell the function to wait for that operation to complete before moving on.

In this example, we’ve turned asyncFn() into an async function. We’ve also prefaced the window.fetch() call with the await operator.

async function asyncFn () {
	await fetch('https://jsonplaceholder.typicode.com/posts/').then(function (response) {
		return response.json();
	}).then(function (data) {
		console.log('Async Fetch', data);
	});
	console.log('Async Message');
}
asyncFn();

When this runs, Async Fetch and the returned data are logged into the console before Async Message. The function waited for the window.fetch() Promise to settle before continuing.

An async function always returns a promise

One side-effect of using the async operator is that an async function always returns a promise, even if you’re not actually making any asynchronous calls in it.

// This returns a promise
async function getTheAnswer () {
	return 42;
}

let answer = getTheAnswer();

Here, answer does not have a value of 42. Instead, it’s value is a resolved promise that you can use Promise.then() and Promise.catch() with.

// logs 42 into the console
answer.then(function (data) {
	console.log(data);
});

You might structure your code differently with async functions

Here’s a function that makes a call to the JSONPlaceholder API’s /posts endpoint.

You pass in a post ID as an argument, and it fetches the data, parses it to JSON, and handles errors.

/**
 * Get an article by its ID
 * @param  {Integer} id The article ID
 */
function getArticleByID (id) {
	fetch(`https://jsonplaceholder.typicode.com/posts/${id}`).then(function (response) {

		// If the response is successful, get the JSON data
		if (response.ok) {
			return response.json();
		}

		// Otherwise, throw an error
		throw 'Something went wrong.';

	}).then(function (data) {
		console.log(data);
	}).catch(function (error) {
		console.warn(error);
	});
}

// Get the article with an ID of 3
getArticleByID(3);

If we convert getArticleByID() into an async function, we can structure it a bit differently.

First, let’s use the await operator with our window.fetch() call, and assign the returned response to the response variable. Our async function will wait until the response is returned before continuing.

async function getArticleByID(id) {

	// Get the post data
	let post = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`);

}

Next, we can check if the response.ok property is true. If it’s not, we’ll throw an error.

If the response is OK, though, we’ll use the await operator with response.json() to get the body JSON data from the response object, and assign it to the data variable. Again, our async function will wait for that to complete before moving on.

Once data is set, we can log it to the console.

async function getArticleByID(id) {

	// Get the post data
	let response = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`);

	// If the call failed, throw an error
	if (!response.ok) {
		throw 'Something went wrong.';
	}

	// Otherwise, get the post JSON
	let data = await response.json();

	// Log the data to the console
	console.log(data);

}

Handling errors with async functions

In our async getArticleByID() function, we throw an error, but don’t actually catch it or handle it anywhere. There are a few ways you can deal with this.

Because an async function always returns a promise, we can chain a Promise.catch() method to it.

// Get the article with an ID of 999999
// log a warning in the console if something goes wrong
getArticleByID(999999).catch(function (error) {
	console.warn(error);
});

That works, but many developers prefer to use a try...catch block inside their async function instead.

async function getArticleByID(id) {
	try {

		// Get the post data
		let response = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`);

		// If the call failed, throw an error
		if (!response.ok) {
			throw 'Something went wrong.';
		}

		// Otherwise, get the post JSON
		let data = await response.json();

		// Log the data to the console
		console.log(data);

	} catch (error) {
		console.warn(error);
	}

}

// Get the article with an ID of 999999
// if there's an error, a warning is logged to the console by the catch() block in the function
getArticleByID(999999);

If you enjoyed this series, you might like my course on APIs and Asynchronous JS.