Async / Await

Open the project folder

In our starter files, open the index.html page from this lessons folder:

10.Async-JavaScript > 06.Async-await

Introduction to JavaScript async / await

With the arrival of ES2017 came a new way to handle async code in JavaScript. Nothing we have looked at previously is redundant, it is all something we need to still know. Particularly for what we are going to look at now, which is async / await.

Async / await uses promises behind the scenes, but it was created to look simpler, and be easier to read for the developer. It looks simpler because we go back to something familiar, which is a function.

Starter file example

In the starter files, we have commented out our random image example as a promise:

// const promise = fetch('https://dog.ceo/api/breeds/image/random').then(
//   function (response) {
//     return response.json();
//   }
// );

This example is for reference only. Using a regular function, we may do something like this to set an image:

function setImage() {
  const response = fetch('https://dog.ceo/api/breeds/image/random');
  console.log(response);
}
setImage();

This example will show the following in the console:

Promise {<pending>}

And as you will know from previous examples, the returned value is a pending Promise. Then as earlier we access the response data using json():

function setImage() {
  const response = fetch('https://dog.ceo/api/breeds/image/random');
  const image = response.json();
  console.log(image);
}

This will lead to an error because the image data is not available just yet:

⚠️ TypeError: response.json is not a function

With promises, we knew we had to chain a then method onto the end. This waited for a successful response, then made the response data available to us.

The async function

With async / await, we have two simple steps. First, we mark the function as async:

async function setImage() {
  const response = await fetch('https://dog.ceo/api/breeds/image/random');
  const image = response.json();
  console.log(image);
}

This turns the function into an async function, this will now return a Promise.

The await keyword

Allowing us to use the await keyword inside the function. Await means we can pause the code at any point inside the async function, like this:

function setImage() {
  // await image until promise fulfills
  const response = await fetch('https://dog.ceo/api/breeds/image/random');
  const image = response.json();
  console.log(image);
}

The await keyword will make the code act like it is synchronous. It will pause the running of the rest of the code until the promise fulfils. In our case, it is the fetch call which is the promise.

The console log will appear as follows:

Promise {<pending>}
  [[Prototype]]:Promise
  [[PromiseState]]:"fulfilled"
  [[PromiseResult]]:Object
    message:"https://images.dog.ceo/breeds/redbone/n02090379_433.jpg"
    status:"success"
    [[Prototype]]:Object

With this response, we must open the Object section to get what we need. This is because we have waited for the response to come back, by using await:

await fetch('https://dog.ceo/api/breeds/image/random');

Once this finishes, we run the json() method and the console.log below:

const image = response.json();
console.log(image);

Remember, the json method also returns a promise, so we are currently trying to access the converted response too early. With promises we would us a then method, but all we need to do here is to again use await:

async function setImage() {
  const response = await fetch('https://dog.ceo/api/breeds/image/random');
  // await response.json()
  const image = await response.json();
  console.log(image);
}

This will again wait on this line to complete before logging the value to the console:

{message: "https://images.dog.ceo/breeds/terrier-border/n02093754_6483.jpg", status: "success"}

Set the image element

And like earlier, we have the image URL in the message, which we can use to set the image element:

async function setImage() {
  const response = await fetch('https://dog.ceo/api/breeds/image/random');
  const image = await response.json();
  document.querySelector('img').src = image.message;
}
setImage();

Using with function expressions and arrow functions

The async / await syntax also works with other function types. Including function expressions and arrow functions:

// function expression
let func = async function() { return "Hey" };

// arrow function
let func = async () => { return "Hey" };

And we then use await inside when needed. Async / await is a clean and powerful solution to handling async tasks. But we need to be careful to not get too carried away.

In this example, we are setting the image on the next line, so it makes complete sense to await the data coming back.

But imagine something like this:

async function setImage() {
  const response = await fetch('https://dog.ceo/api/breeds/image/random');
  const response2 = await fetch('https://dog.ceo/api/breeds/image/random');

  const image = await response.json();
  document.querySelector('img').src = image.message;
}
setImage();

If we were making two parallel requests for data, both independent of each other, it would not make sense to request the first, wait for the data to come back, then request the second. We could cause an unnecessary delay before we set the image.

We can use await when we need to wait on data that we need soon after, but we don’t want to block the rest of the code running if we don’t need to.

Handling multiple requests like this is something we will look at next in more detail, along with error handling in async / await.