Wednesday-Friday: Async and Await Copy

Introduction

When we have a lot of things going on in our code following the asynchronous code can be tricky. For this reason we have two keywords async and await that help us deal with this problem. These two keywords can help make asynchronous read more like synchronous code. Thus our code looks cleaner while keeping the benefits of asynchronous code.

Learning Objectives

  1. How we declare an async function
  2. What the async keyword does
  3. What the await keyword does
  4. What is returned from an async function
  5. What happens when an error is thrown inside an async function
  6. How we handle errors inside an async function

Let’s start with an example. In both cases we have a function that gets some information from a server, processes it, and returns a promise. Both functions do the exact same thing.

function getCountiesInfo(name) {
  return server.getCountries().then(countries => {
    return countries.find(country => { return country.name === name });
  });
}
async function getCountriesInfo(name) {
  const countries = await server.getCountries();
  const country = countries.find(country => { return country.name === name });
  return country;
}

Have you noticed the async keyword before the function declaration? How about the await keyword before server.getCountries()?

The Async Keyword

In order to let javaScript engine know that we are declaring an asynchronous function we need to use the keyword async before that function. Inside that function it is required to use await before the functions. When we are declaring a function with the async, it automatically returns a promise. Returning in an async function is the same as resolving a promise, likewise, throwing an error will reject the promise.

Async functions are just syntactical sugar for promises. The async keyword can also be used with any of the ways a function can be created. In other words, it is valid to use an async function anywhere we can use a normal function.

Now let’s see some examples. It’s ok if you don’t understand them.You can come back and take a look when you have gone through more stuff.

  const myAsyncFunc = async () => {
    // do something asynchronously and return a promise
    return result;
  }
 myArray.forEach(async item => {
   // do something asynchronously for each item in 'myArray'
   // one could also use .map here to return an array of promises to use with 'Promise.all()'
 });
server.getAnimals().then(async animals => {
  animals.forEach(animal => {
    // do something asynchronously for each animal
  });
});

The Await Keyword

The await keyword tells javaScript to wait for an asynchronous action to finish before continuing the function. The await keyword is used to get a value from a function where we would normally use .then(). In other words, instead of calling .then() after the asynchronous function, we would simply assign a variable to the result using await. Then we can use the result in our code as we would in our synchronous code.

Handling Errors

Handling errors in async functions is very easy. As we saw in promises, promises have the .catch() method for handling rejected promises. Since async functions just return a promise, we can simply call the function, and append a .catch() method to the end.

asyncFunctionCall().catch(err => {
  console.error(err)
});

There is also another way that we can handle errors in async functions. We can use the try/catch block just like we would inside synchronous code. Thus we can handle the error directly inside the async function.

async function getCountriesInfo(name) {
  try {
    const countries = await server.getCountries();
    const country = countries.find(country => { return country.name === name });
    return country;
  } catch (error) {
    // Here you can handle the error any way you'd like
  }
}

This way it can look messy, however, it is very easy to handle errors without appending .catch() after your function calls. Keep in mind that the way you handle errors is up to you and which method you use should be determined by how your code was written. Over time you will get more comfortable of what needs to be done.

Time for some practice!!!

Time for Practice

Now we are going to take the Giphy API and convert the promise based code into async/await compatible code.

Here’s a refresher of the code we are starting with:

<script>
  const img = document.querySelector('img');
  fetch('https://api.giphy.com/v1/gifs/translate?api_key=YOUR_KEY_HERE&s=cats', {mode: 'cors'})
    .then(function(response) {
      return response.json();
    })
    .then(function(response) {
      img.src = response.data.images.original.url;
    });
</script>

Await does not work on the global scope. Therefore we will have to create an async function that wraps our API call to Giphy.

<script>
  const img = document.querySelector('img');

  async function getCats() {
    fetch('https://api.giphy.com/v1/gifs/translate?api_key=YOUR_KEY_HERE&s=cats', {mode: 'cors'})
      .then(function(response) {
        return response.json();
      })
      .then(function(response) {
        img.src = response.data.images.original.url;
      })
  }
</script>

Now we have a function that is asynchronous. So we can start refactoring from using promises to using await:

<script>
  const img = document.querySelector('img');

  async function getCats() {
    const response = await fetch('https://api.giphy.com/v1/gifs/translate?api_key=YOUR_KEY_HERE&s=cats', {mode: 'cors'});
    response.json().then(function(response) {
      img.src = response.data.images.original.url;
    });
  }
</script>

Since response is still the same object we have passed to the .then() block at the start, we still need to use the .json() method, which in turn returns a promise. Since .json() returns a promise, we can use await to assign the response to a variable.

<script>
  const img = document.querySelector('img');

  async function getCats() {
    const response = await fetch('https://api.giphy.com/v1/gifs/translate?api_key=YOUR_KEY_HERE&s=cats', {mode: 'cors'});
    const catData = await response.json();
    img.src = catData.data.images.original.url;
  }
</script>

Now in order to use this function, we need to call it with getCats() in our code.

<script>
  const img = document.querySelector('img');

  async function getCats() {
    const response = await fetch('https://api.giphy.com/v1/gifs/translate?api_key=YOUR_KEY_HERE&s=cats', {mode: 'cors'});
    const catData = await response.json();
    img.src = catData.data.images.original.url;
  }
  getCats();
</script>

Although this code looks a bit different after refactoring, it will behave exactly the same as in the previous topic. When it comes to cleaning up asynchronous javascript code, async/await are very useful tools. Keep in mind that async/await are just promises written in a different way.

Study

Now let’s dive deeper into the understanding of async/await.

  1. Read this article for a solid introduction to async/await. Also this article has some good examples of it’s use.

  2. Watch the next video for a good overview on async/await and it’s purpose, along with a special trick.

  1. If you feel brave enough, read this article for a more in-depth look at async/await, including how to handle errors. (Student note: might be far too advanced for now, since it mentions documents databases and several other backend stuff!)

Advanced Async/Await

When using the async/await pair in a loop, and especially in a loop callback function such as in foreach or `map, things start to get more complicated. Take a long break and read this interesting and important article on JavaScript async and await in loops.

Additional Resources

In this section you can find a lot of helpful links to other content. This is a supplemental material for you if you want to dive deeper into some concepts.

  1. The following video is an example of how you can change callbacks, to promises, to async/await.

  1. The following video gives a comprehensive view of promises, async, and await.


Leave a Reply