Lesson Tuesday

In this lesson, we'll cover async functions, a relatively new JavaScript feature that was added in ES 2017. (Remember that ES6 is EcmaScript 2015.) While you aren't required to use async functions for the independent project, they are a really useful piece of JavaScript functionality and definitely a tool you should know about and be familiar with.

An async function allows us to write asynchronous code as if it were synchronous. This can make our code more concise. And while promises go very well with API calls, there are plenty of other situations where using async functions will handle asynchronous operations better than a promise will. For example, if we wanted to do a series of five or six things in a row, many but not all of them async, code with promises would be very verbose while async functions would be easier to read and reason about.

In fact, even the code from the last lesson chains together two async operations. Throw another API call into the mix that also uses fetch() and we are talking about four async operations in a row when we consider the streaming data as well. It's very feasible that we'd do a few synchronous things in between(such as parsing data from the first API call).

Before we update our weather API project to use async functions, let's take a look at an example. We can create an async function with the async keyword. Try the following code in the console:

async function thisIsAsync() { return "This is async"; }
thisIsAsync();
> PromiseĀ {<resolved>: "This is async"}

All we did here is add the async keyword to a basic function that returns a string. When we call the function, it returns a resolved promise. As this example shows, async functions are using promises under the hood, too!

The real power of async functions lies with the await keyword. When await is used within an async function, our code will stop executing until the line of code that includes await is completed. Let's take a look at an example in the context of an API call that uses fetch():

async function makeApiCall() {
  const response = await fetch("http://some-api-call.com");
  const jsonifiedResponse = await response.json();
  return jsonifiedResponse;
}

We start by adding the async keyword to our makeApiCall() function. Now we can use the await keyword. Note that we can't use the await keyword outside of an async function. If we try to, we'll get the following error: Uncaught SyntaxError: await is only valid in async function.

Next, we set the value of the response variable to be equal to the response of the API call. If we did this without the await keyword, response would be undefined. That's because the variable is assigned before the async fetch() call is complete.

Once we add the await keyword, though, the value of response won't be assigned until the fetch() call is resolved.

We also need to await the completion of the json() method because it's an async operation, too. Once that's done, we're ready to return the final jsonifiedResponse. The await keyword has given us the ability to write asynchronous code as if it were synchronous. This results in very concise code that is easy to read and understand.

Now that we've looked at a basic example, let's update our weather application to use async and await.

We'll start by updating our service logic:

src/weather-service.js

export default class WeatherService {  
  static async getWeather(city) {
    try {
      const response = await fetch(`http://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${process.env.API_KEY}`);
      if (!response.ok) {
        throw Error(response.statusText);
      }
      return response.json();
    } catch(error) {
      return error.message;
    }
  }
}

First, we update our static method to be async. Next, we need to wrap our code in a try...catch block. We aren't resolving or rejecting promises here so we need to handle any errors ourselves.

Inside the try block, we'll make our API call with fetch() and then use the await keyword to wait for the API call to complete. The rest of the service code is unchanged - we throw an error if the response property isn't ok. If the response property is ok, we return the streaming data.

Next, let's take a look at our user interface logic:

src/main.js
// Imports and clearFields() omitted for brevity.

function getElements(response) {
  if (response.main) {
    $('.showHumidity').text(`The humidity in ${response.name} is ${response.main.humidity}%`);
    $('.showTemp').text(`The temperature in Kelvins is ${response.main.temp} degrees.`);
  } else {
    $('.showErrors').text(`There was an error: ${response}`);
  }
}

async function makeApiCall(city) {
  const response = await WeatherService.getWeather(city);
  getElements(response);
}

$(document).ready(function() {
  $('#weatherLocation').click(function() {
    let city = $('#location').val();
    clearFields();
    makeApiCall(city);
  });
});

We've hardly got any code inside the $(document).ready() section of our code now but that's actually a good thing! Our code is modular and separated into functions.

We now have a new async function called makeApiCall(). Inside that function, we await a response to our API call and then run our callback once the data stream from the API response is complete. Remember that we can only use the await keyword inside an async function, which is why we need to separate out the code like this.

We've also made a very small update to the template literal of the error message in our getElements() callback, too, because our static async method returns the error message if it fails, which means we just need to grab the ${response} in the error conditional of the callback.

And that's it!

Async functions can be a concise and elegant way to write code. However, it's important to think carefully about the code we put inside async functions. We are essentially forcing any code inside an async function to run synchronously. That means the code inside an async function is blocking. If we put too much code inside an async function, we risk overriding JavaScript's non-blocking capabilities, potentially making our code more inefficient. That's not an issue in the code above but it would be if we wrapped our entire application in an async function.

You are welcome (but not required) to use async functions for this section's independent project. You are also encouraged to practice writing async functions during the classwork. However, make sure you have a clear understanding of promises first. After all, async functions use promises under the hood.


Example GitHub Repo for API Project with fetch()

Lesson 23 of 29
Last updated April 8, 2021