How to use the async-await syntax in your asynchronous code

Jorge Vergara pointing at a target showing an arrow in the bullseye

Do you keep running into Cannot read property X of undefined? If you’re writing JavaScript/TypeScript you probably do since the language is almost entirely asynchronous.

Most of the time is because we forgot to return a promise inside of a .then() or because we wrote the code inline without knowing it returned a promise.

In this post we’re going to talk through a new syntax for asynchronous code called async/await and it’s for handling Promises in JavaScript.

Flow control in JavaScript is hard, and sometimes the code doesn’t happen in the order you’re expecting it to happen.

For example, if you write this:

const user = myProvider.getUserProfile();

console.log(user);

You might expect the code to fetch the user’s profile and then log it to the console, but in reality, that code probably will write undefined to the console and then get the user’s profile from the provider.

Things like this happen because JavaScript is almost entirely asynchronous.

In the past, this was handled with callbacks. Callbacks are just the name of a convention for using JavaScript functions. There isn’t a particular thing called a ‘callback’ in the JavaScript language; it’s just a convention.

Instead of immediately returning some result like most functions, functions that use callbacks take some time to produce a result. The word ‘asynchronous,’ aka ‘async’ just means ‘takes some time’ or ‘happens in the future, not right now.’

Unfortunately, asynchronous JavaScript, or JavaScript that uses callbacks, is hard to get right intuitively. A lot of code ends up looking like this:

fs.readdir(source, function (err, files) {
  if (err) {
    console.log('Error finding files: ' + err);
  } else {
    files.forEach(function (filename, fileIndex) {
      console.log(filename);
      gm(source + filename).size(function (err, values) {
        if (err) {
          console.log('Error identifying file size: ' + err);
        } else {
          console.log(filename + ' : ' + values);
          aspect = values.width / values.height;
          widths.forEach(
            function (width, widthIndex) {
              height = Math.round(width / aspect);
              console.log('resizing ' + filename + 'to ' + height + 'x' + height);
              this.resize(width, height).write(dest + 'w' + width + '_' + filename, function (err) {
                if (err) console.log('Error writing file: ' + err);
              });
            }.bind(this)
          );
        }
      });
    });
  }
});

See the pyramid shape and all the }) at the end? This is what in the past most JavaScript programmers referred to as callback hell.

To mitigate callback hell, JavaScript started implementing what we know today as Promises.

A Promise is like an IOU for something that IS going to happen. You just don’t know when. It can be now or any time in the future.

So instead of something like this:

getData(function(x){
    getMoreData(x, function(y){
        getMoreMoreData(y, function(z){
            ...
        });
    });
});

You’d use something like this:

getData()
  .then(function getMoreData(x) {})
  .then(function getMoreMoreData(y) {});

Which we all can agree is a more readable way to work. The thing about Promises is that the code somewhat resembles callbacks, and people started panicking about callback hell again. (Yeah, people are weird)

That’s where async/await comes into play, async/await is still based on Promises, but with a new syntax that’s more readable.

All you need to do to start using it is to mark a function as async, then you use await in front of every line you want to ‘wait for.’

The await keyword waits until the line resolves to move to the next line.

So if we want to fetch for a user profile and then, log it to the console, we can do something like this:

async logUserProfile(): Promise<void> {
  const userProfile = await myProvider.getUserProfile();

  console.log(userProfile);
}

The line: console.log(userProfile) won’t run until the previous expression marked as await completely resolves.

There is one catch, if we don’t think things through we can end up with less performant code, for example:

async getProfile(){
  const jorge = await myProvider.getUserProfile('jorge');
  const evelyn = await myProvider.getUserProfile('evelyn');

  console.log(jorge, evelyn);
}

The previous block of code will run synchronously, meaning it won’t ask for evelyn until it gets the result for jorge.

So if the first Promise takes 2 seconds, and the second Promise takes 3 seconds, the function will take a total of 5 seconds before calling the next line to log the values in the console.

We need to take advantage of JavaScript’s asynchronous nature, in that example, it would be better to use something like this:

async getProfile(){
  const jorgePromise = myProvider.getUserProfile('jorge');
  const evelynPromise = myProvider.getUserProfile('evelyn');

  // This waits for both.
  // If one takes 2 seconds and the other 3 seconds it will move to the
  // next line after the 3 seconds.
  const [jorge, evelyn] = await Promise.all([jorgePromise, evelynPromise]);

  console.log(jorge, evelyn);
}

The Promise.all() expression will resolve both promises before trying to log the values to the console, but it won’t wait for one to finish before starting the other, it will trigger both.

For example, if the first call takes 2 seconds, and the second call takes 3 seconds, we’ll only wait a total of 3 seconds because both calls are happening asynchronously.

Error handling using async/await

The most straightforward way to handle errors here is using a try/catch block.

We run every await Promise inside the try block and handle the errors inside the catch, for example:

async getProfile(){
  try{
    const jorgePromise = myProvider.getUserProfile('jorge');
    const evelynPromise = myProvider.getUserProfile('evelyn');

    const [jorge, evelyn] = await Promise.all([jorgePromise, evelynPromise]);
    console.log(jorge, evelyn);
    } catch(error) {
      console.error(error);
    }
}

That way if something unexpected happens it will catch it and log it to the console.

What do you think, do you like the async/await syntax more or the regular promises? Shoot me an email and let me know :-)