Async/Await Pattern

There's a special syntx to work with promises in a more comfortable fashion, called async/await. It's surprisingly easy to understand and use.

Async functions

Let's start with the async keyword. It can be placed before a function, like this:

async function f() {
  return 1;
}

The word async before a function means one simple thing: a function always returns a promise. Other values are wrapped with a resolved promise automatically.

for instance, this function returns a resolved promise with the result of 1; let's test it:

async function f() {
  return 1; // equivalent to return Promise.resolve(1);
}

f().then((data) => console.log(data));
1

So, async ensures that the function returns a promise, and wraps non-promises in it.

Await

The keyowrd await makes sure javascript wait unitl promise settles and returns a result. await works only inside async function.

The syntax:

let value = await promise;

Fetching a post from jsonplaceholder using async/await:

async function f2() {
  let promise_object_post_one = fetch(`https://jsonplaceholder.typicode.com/posts/1`);
  let response = await promise_object_post_one; // (#1)
  let data = await response.json(); // (#2)
  console.log(data.title);
}

f2();
sunt aut facere repellat provident occaecati excepturi optio reprehenderit

The function execution "pauses" at line (#1) and resumes when the promise settles, with the response. Then again the function execution "pauses" at line (#2) and resume when the proise settles with the data.

That doesn't cost any CPU resources, because the Javascript engine can do other jobs in the meantime.

Its just a more elegant syntax of getting the promise result than promise.then, easier to read and write.

Error handling

We can catch that error using try..catch

Example:

async function f3() {
  try {
    let response = await fetch('http://no-such-url');
  } catch (err) {
    console.log(err); // TypeError: failed to fetch
  }
}

f3();

If we dont have try..catch, then the promise generated by the call of the async function f3() becomes rejected. We can append .catch to handle it.

f3()
  .then((response) => {console.log(response)})
  .catch((err) => {console.log(err)})
TypeError: failed to fetch

if we forget to add .catch even there, then we get unhandled promise error. We can catch such error using a global unhandledrejection event handler.

Using await in for await..of loop

async function moviePlanets (movieNum) {
  let films_url = 'https://swapi.dev/api/films/';

  let movie_response = await fetch(films_url + movieNum + '/');
  let movie_data = await movie_response.json();
  console.log('Movie title: ', movie_data.title);

  let planets = movie_data.planets;
  let planets_promise_array = planets.map((url) => fetch(url).then((response) => response.json()));
  
  // normal loops like for..each doesnot work.
  for await (let pl of planets_promise_array) {
    console.log('planet: ', pl.name);
  }
}

moviePlanets(1);

throw

We can use throw to create an exeption if we need one, if we are using the try..catch mechanism.


async function moviePlanets(movieNum) {
  let films_url = 'https://swapi.dev/api/films/';
  try {
    if (typeof movieNum !== 'number') {
      // We can use throw to create an exeption if we need one. 
      throw "You must pass in a number.";
    }
    let movie_response = await fetch(films_url + movieNum + '/');
    let movie_data = await movie_response.json();
    console.log('Movie title: ', movie_data.title);

    let planets = movie_data.planets;
    let planets_promise_array = planets.map((url) => fetch(url).then((response) => response.json()));

    // normal loops like for..each doesnot work.
    for await (let pl of planets_promise_array) {
      console.log('planet: ', pl.name);
    }
  } catch (e) {
    console.error('Error: ', e)
  }
}

moviePlanets(1);

Using async function at the top level code

We can use an IIFE to do that:

(async function () {
  let response = await fetch(`https://jsonplaceholder.typicode.com/todos/1`);
  let obj = await response.json();
  console.log(obj);
})();

Example with Promise.all

let blog_posts = [
  'https://jsonplaceholder.typicode.com/posts/1',
  'https://jsonplaceholder.typicode.com/posts/2',
  'https://jsonplaceholder.typicode.com/posts/3'
];

let blog_posts_promise_objects = blog_posts.map((post) => fetch(post));

(async function () {
  let responses = await Promise.all(blog_posts_promise_objects);
  console.log(responses);
})();

Async on object methods

Just use the async keyword before the object method:

let user_obj = {
  first_name: 'John',
  last_name: 'Doe',
  async get_todo() {
    let response = await fetch(`https://jsonplaceholder.typicode.com/todos/1`);
    let obj = await response.json();
    console.log(obj);
  }
}

user_obj.get_todo();

Async on ES6 class methods

Just use the keyword async before the class method:

class Todos {
  async get_todo() {
    let response = await fetch(`https://jsonplaceholder.typicode.com/todos/1`);
    let obj = await response.json();
    console.log(obj);
  }
}

var myTodo = new Todos();
myTodo.get_todo();

Performace considerations

Keep async functions small or we may end up blocking the execution of code that need not be blocked. Remeber, async/await pattern allows us to use async code. It does not cause your code to become asynchronous.

Summary

The async keyword before a function has two effects: