JavaScript: async/await

Subscribe to my newsletter and never miss my upcoming articles

The ECMAScript 2017 (ES8) specification introduced async/await to act as syntactic sugar for working with Promises. It was meant to make working with Promises simple. Hence, in order to start using or learning about async/await, you need to have knowledge about JavaScript Promises. I've already published a detailed article on the topic and you can read it from here.

The async keyword

The async keyword is written before the keyword function when declaring a function. It turns the function into an async function.

An async function is simpy a function that knows to expect the await keyword somewhere in its definition block. The very basic meaning is that, the async keyword makes a function asynchronous, and allows it to be able to use its associative keyword await.

Here's how you define an async function

async function foo() {
    // function body
}

The async keyword written in front of the function keyword simply means:

  • The function is now asynchronous; it's not a regular function.
  • The function returns a Promise.
  • The function has the ability to use the await keyword.

Don't worry about the await keyword, it'll make sense soon.

Earlier on, I mentioned that async/await makes using Promises easier and simpler. But in my last article on Promises, I did mention that the Promises feature was introduced as the best way to write asynchronous code - better than using Callback Functions. So, you'd probably be wondering: how better could async/await be than Promises?

Let's use an example to demonstrate.

Example

The function below checks if a number is a perfect square and returns its root. The result (square root) is resolved by a Promise after 3 seconds ~ the global setTimeout() is used.

Tip:

I'll be using the log() function which prints stuff on the DOM of the Codepen iFrame. If you've read my previous articles you'd already be familiar with this function. But feel free to use any method of your choice.

function rootOf(number) {
    return new Promise(function (resolve) {
        // checks if the number is greater than zero (and positive) and whether or not the number is a perfect square
        let isPerfectSquare = number > 0 && Math.sqrt(number) % 1 === 0;

        if (isPerfectSquare) {
            // if the number is a perfect square, resolve its root after 3 seconds (3000 milliseconds)
            setTimeout(function(){
                resolve(Math.sqrt(number));
            }, 3000);
        }
    });

}

// let's test this function
rootOf(25).then(function(root){
    console.log(root);     // logs: 5
});

Now let's try and sum the roots of 36, 49, and 64:

let sumOfRoots = rootOf(36) + rootOf(49) + rootOf(64);

// print sumOfRoots
console.log(sumOfRoots); 

// [object Promise][object Promise][object Promise]

Instead of evaluating 6 + 7 + 8 and then 21, we get this in the console:

[object Promise][object Promise][object Promise]

Now, this is because at the time we log the sumOfRoots to the console, the three Promises had not settled yet. So, JavaScript goes ahead and performs the subsequent codes. We'd have to wait for all the Promises to settle before logging the value to the console.

To do this, we'll define a function which returns a Promise. We'll nest the rootOf() Promises for each perfect square and then in the last code block, after all the Promises have settled, resolve the sum of all the resolved square roots from the rootOf() Promises.

Let's code!

function getSumOfRoots(num1, num2, num3) {
    return new Promise(function (resolve) {
      rootOf(num1).then(function(root1) {
        rootOf(num2).then(function(root2) {
          rootOf(num3).then(function(root3) {
            // now let's resolve the getSumOfRoots() Promise with the sum of all the roots
            resolve(root1 + root2 + root3);
          });
        });
      });
    });
}

// now we can test our getSumOfRoots() Promise
getSumOfRoots(36, 49, 64).then(function(sumOfRoots){
  console.log(sumOfRoots)
});

// logs: 21

What does the nesting remind you of? Callback hell? Yeah, scary right? 😅

Our code works fine. The only problem is it becomes hard to quickly understand, plus we had to write so many lines just to achieve this. I even drank 2 bottles of water after writing this function. 😅

Let's run from Promises, shall we?

JavaScript async/await makes using Promises a cup of milk! We'll only need four lines in the getSumOfRoots() function block to achieve the same result. Also, the code becomes more readable and easily understandable. That brings us to the second keyword in the topic: await.

The await keyword

Let's take a look at the following sentences:

  1. The graduates await their results.
  2. Let's drink as we await the good news!
  3. A marvellous reception awaited me on my first day at work.

From the sentences above what conclusion can you draw? Simple! A process is taking time to settle, or a process needs time to settle, or (my favourite part), a process is pending!

In JavaScript, which type of objects have a status of pending? Promises! Right.

The await keyword is used to wait for a Promise to settle.

Usually, when we try to perform a tast in a Promise, JavaScript goes ahead and executes the following codes while it runs the pending code in the background, and when this background process completes, JavaScript deals with it the way it is programmed to.

The await keyword kind of alters that default behaviour in the async function in which it was used. It makes JavaScript "wait" for the Promise, before which it was written, to settle before continuing to run other codes.

The syntax

async function foo() {
    // syntax ...
    [settledValue] = await [pending Code];
};

Let's explain the above syntax:

  • The [settledValue] simply stores the settled value from the pendingCode.
  • The [pendingCode] is the Promise. If the Promise is rejected, the await expression throws an error. And if the expression after the await keyword is not a Promise, JavaScript converts it to a resolved object with the static Promise.resolve() method.

Note

The await keyword must only be used in async functions. Using it outside an async function will throw a SyntaxError.

Let's go for a test run!

Remember the over-nested getSumOfRoots() function which looked like callback hell? Good! Let's re-write it with async/await.

async function sumOfRootsAsync(num1, num2, num3) {
    let root1 = await rootOf(num1);
    let root2 = await rootOf(num2);
    let root3 = await rootOf(num3);

    return root1 + root2 + root3;
}

Done! Yes, done - just like that! Notice that syntactic sugar taste in your mouth? 😁

Now, since async functions return Promises, let's use the .then() method to grab our sum of square roots, which resolves because of the last return statement.

sumOfRootsAsync(36, 49, 64).then(function(sum) {
  console.log(sum);
});

Awesome right? Let's digest this:

  • let root1 = await rootOf(num1);
    
    The await keyword makes JavaScript wait for the rootOf() Promise to settle then its settled value is stored in the root1 variable. In this case, the root of 36 = 6.
  • let root2 = await rootOf(num2);
    
    The await keyword makes JavaScript wait for the rootOf() Promise to settle then its settled value is stored in the root2 variable. In this case, the root of 49 = 7.
  • let root3 = await rootOf(num3);
    

    The await keyword makes JavaScript wait for the rootOf() Promise to settle then its settled value is stored in the root3 variable. In this case, the root of 64 = 8.

  • return root1 + root2 + root3;
    

    This line of code, in async functions, is the way of saying: resolve(root1 + root2 + root3). In this case 6 + 7 + 8 = 21.

Cool. But... Let's use an alternative method. We can use the array .reduce() method and the static Promise.all() method to rewrite the above async function.

Tip:

The static Promise.all() method has been covered in my article on Promises. Basically, it accepts an iterable object of Promises (preferrably an array of Promises) as a parameter, waits for all of the Promises to settle, and then, it settles with an array of the results from the settled Promises in its paramater.

async function sumOfRootsAsync(num1, num2, num3) {
    let roots = await Promise.all( [rootOf(36), rootOf(49), rootOf(64) ]);

    let sumOfRoots = roots.reduce(function(total, currentValue) {
        return total + currentValue;
    });

    return sumOfRoots;
}

// print the resolved value
sumOfRootsAsync(36, 49, 64).then(function(sum) {
  log(sum);
});

Okay, cool!

But, what if there are errors?

Handling Errors in async Functions

There are three ways you can handle errors in async functions. You can do so by using:

  • try...catch.
  • the optional, second callback function of the .then() method.
  • .catch().

Error-handling with try..catch

Before we proceed to handling errors we need to alter our rootOf() function so it can be able to make a rejection (error). At the moment it only resolves with the root of the perfect square. Let's change that.

function rootOf(number) {
    // now passing the reject argument to the executor
    return new Promise(function (resolve, reject) {
        // checks if the number is greater than zero (and positive) and whether or not the number is a perfect square
        let isPerfectSquare = Math.sqrt(number) % 1 === 0;

        if (isPerfectSquare) {
            // if the number is a perfect square, resolve its root after 3 seconds (3000 milliseconds)
            setTimeout(function(){
                resolve(Math.sqrt(number));
            }, 3000);
        } else {
            // if not a perfect square, reject with a new Error 
            reject(new Error(number + ' is not a perfect square!'));
        }
    });

}

I assume you already have knowledge of the try..catch structure as it's a basic method of debugging code in JavaScript.

async function sumOfRootsAsync(num1, num2, num3) {
    try {
      let root1 = await rootOf(num1);
      let root2 = await rootOf(num2);
      let root3 = await rootOf(num3);

      // instead of return, let's log directly
      console.log(root1 + root2 + root3);

   } catch(err) {
      console.error(err);
   }
}

// intentionally passing a number (8) which is not a perfect square

sumOfRootsAsync(8, 49, 64);

Error-handling with the Second Callback of .then() Method

The .then() method takes two callback functions: the first is executed when the Promise settles with success; the second catches errors - which we'll be making use of. In the example below we won't log the error directly.

async function sumOfRootsAsync(num1, num2, num3) {
    let root1 = await rootOf(num1);
    let root2 = await rootOf(num2);
    let root3 = await rootOf(num3);

   return root1 + root2 + root3;
}

sumOfRootsAsync(8, 49, 64).then(function(sum) {
  log(sum);
}, function(error){
  log(error);
});

Error-handling with the .catch() Method

We can chain the usual .catch() method to the returned async-function Promise.

async function sumOfRootsAsync(num1, num2, num3) {
    let root1 = await rootOf(num1);
    let root2 = await rootOf(num2);
    let root3 = await rootOf(num3);

   return root1 + root2 + root3;
}

sumOfRootsAsync(36, 41, 64)
  .then(function(sum) {
      log(sum);
  })
  .catch(function(error){
      log(error);
  });

This brings us to the end of our discussion.


End Notes

Asynchronous JavaScript is a very interesting topic in the language, and sometimes for beginners it could be a lot of fun.

Perhaps, later in the future I might publish new articles in asynchronous JavaScript.

But until then, please keep your eyes on this blog. We've got a lot to learn together.


Comments (1)

foreign-bride's photo

Great, I agree with you. foreign-brides.net - here you can meet and start a serious relationship. Everything is very simple. Come and register, your love is waiting for you.