JavaScript async and await - in plain English, please

Tapas Adhikary - Aug 20 '21 - - Dev Community

If you found this article helpful, you will most likely find my tweets useful too. So here is the Twitter Link to follow me for information about web development and content creation. This article was originally published on my Blog.


JavaScript developers love using async-await. It is the most straightforward way to deal with asynchronous operations in JavaScript. Suppose we do a poll of usability between the async/await syntax vs. the promise.then()...then().catch(), async/await going to win with a significant margin. However, we may ignore something important here.

It's not just about the syntax and usability that we need to compare them with. We shouldn't even compare async/await and the plain-old way of handling promises. There are various use-cases and chances that we may use them together. Also, the understanding of promises is the essential part of appreciating the existence of async/await.

Welcome to the third article of the series, Demystifying JavaScript Promises - A New Way to Learn. Let's start discussing async/await. If you are new to the series, please feel free to check out the previous posts,

If you like to learn the async/await keywords from video content as well, this content is also available as a video tutorial here: 🙂

Please feel free to subscribe for the future content

The async/await are Keywords

JavaScript offers us two keywords, async and await, to make the usage of promises dramatically easy. The async and await keywords contribute to enhance the JavaScript language syntax than introducing a new programming concept.

In plain English,

  • We use async to return a promise.
  • We use await to wait and handle a promise.

Let's expand it further to understand the concepts better.

  • The async keyword is for a function that is supposed to perform an asynchronous operation. It means the function may be taking a while before it finishes execution, returns a result, or throw an error.

We use the async keyword with a function as,

 async function fetchUserDetails(userId) {
      // pretend we make an asynchronous call
     // and return the user details
     return {'name': 'Robin', 'likes': ['toys', 'pizzas']};
 }
Enter fullscreen mode Exit fullscreen mode

With the arrow function,

 const fetchUserDetails = async (userId) => {
     // pretend we make an asynchronous call
    // and return the user details
    return {'name': 'Robin', 'likes': ['toys', 'pizzas']};
 }
Enter fullscreen mode Exit fullscreen mode

So, what does the async function fetchUserDetails returns when we invoke it? It returns a Promise.

async-retuns.png

The difference between a regular function and an async function is, the latter always returns a promise. If you do not return a promise explicitly from an async function, JavaScript automatically wraps the value in a Promise and returns it.

  • The await keyword is for making the JavaScript function execution wait until a promise is settled(either resolved or rejected) and value/error is returned/thrown. As the fetchUserDetails async function returns a promise, let us handle it using the await keyword.
 const user = await fetchUserDetails();
 console.log(user)
Enter fullscreen mode Exit fullscreen mode

Now, you will see the returned user object in the console log,

await-result

You would have used the plain-old .then() method to handle that promise without the await keyword.

 fetchUserDetails().then((user) => console.log(user));
Enter fullscreen mode Exit fullscreen mode

A Few Rules about using async/await

We need to understand a few simple rules to use the async and await keywords.

  • You can not use the await keyword in a regular, non-async function. JavaScript engine will throw a syntax error if you try doing so.
 function caller() {
   // Using await in a non-async function.
   const user = await fetchUserDetails();
 }

 // This will result in an syntax error
 caller();
Enter fullscreen mode Exit fullscreen mode
  • The function you use after the await keyword may or may not be an async function. There is no mandatory rule that it has to be an async function. Let's understand it with the following examples,

Create a non-async function that returns the synchronous message, say, Hi.

 function getSynchronousHi() {
    return 'Hi';
 }
Enter fullscreen mode Exit fullscreen mode

You can still use the keyword await while invoking the above function.

 async function caller() {
    const messageHi = await getSynchronousHi();
    console.log( messageHi);
 }

 caller(); // Output, 'Hi' in the console.
Enter fullscreen mode Exit fullscreen mode

As you see, we can use the await with a non-async function but, we can not use it within(or inside) a non-async function.

  • The V8 engine(version >= 8.9) supports the top-level await in modules. It means you are allowed to use it outside of an async function. The Chrome DevTools, Node.js REPL support the top-level await for a while now. However, it is still not supported beyond the environments we just discussed.

To use the top-level await in a non-supported environment, the solution is to wrap it into an anonymous function, like this,

 (async () => {
     const user = await fetchUserDetails();
 })();
Enter fullscreen mode Exit fullscreen mode

How to Handle Errors with async/await?

We learned about error handling using the .catch() handler method in the promise chain article. If the promise rejects, it throws the error, and we need to catch it to handle it.

With the async/await keywords, we can handle the error with traditional try...catch. When there is an error, the control goes to the catch block. Please have a look at the example below.

Assume we have a function that validates if the userId and password are blank. If so, throw an error by rejecting the promise. Otherwise, resolve it with a success message.

const validateUser = ({userId, password}) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (userId && password) {
                resolve(`${userId} you have been authenticated successfully!!!`);
            } else {
                reject({message: 'userId or Password could be blank!'});
            }

        }, 2000);
    });
}
Enter fullscreen mode Exit fullscreen mode

As the above method returns a promise, we can handle it using the await keyword. Let us focus on the case where we pass the userId and password as empty strings.

const app = async () => {
    const data = {
        userId: '',
        password: ''
    };

    try {
        console.log('Initializing...');
        const result = await validateUser(data);
        console.log(result);
    } catch (e) {
        console.error(e.message);
    }
}

// invoke the function app
app();
Enter fullscreen mode Exit fullscreen mode

When we invoke the app() function, the validateUser(data) will throw an error implicitly. We handle it using the try...catch in the app() function. The control will go to the catch block. We will get the error log as,

error-catch.png

If we pass valid userId and password values, we will see the expected result log in the console.

Can We Write the PizzaHub example with async/await?

Surely, I think that's a great idea. We have created APIs and the methods to handle the pizza order in the Robin and the PizzaHub Story. Remember the orderPizza() function? We handled the promises using the .then() and .catch() handler methods.

Let's rewrite the orderPizza() function using async/await. You bet, it is a much simplified version as we see below,

async function orderPizza(type, name) {
    try{
        // Get the Nearby Pizza Shop
        const shopId = await fetch("/api/pizzahub/shop", {
            'lang': 38.8951 , 
            'lat': -77.0364});
        // Get all pizzas from the shop  
        const allPizzas = await fetch("/api/pizzahub/pizza", {
            'shopId': shopId});
        // Check the availability of the selected pizza
        const pizza = await getMyPizza(allPizzas, type, name);
        // Check the availability of the selected beverage
        const beverage =  await fetch("/api/pizzahub/beverages", {
            'pizzaId': pizza.id});
        // Create the order
        const result = await create("/api/order", {
                beverage: beverage.name,
                name: name,
                type: type,
            });
        console.log(result.message);
    } catch(error){
        console.error(error.message);
    };
}
Enter fullscreen mode Exit fullscreen mode

Please find the complete source code from here. So now you know how to write the orderPizza() function using both the async/await and plain-old promises respectively.

Do you want to guess or try how it would look using the JavaScript callback functions? Please take a look from here. I hope you appreciate the world of promises and async/await a lot more now 😀.

So, What's Next?

Thank you for your effort to learn and master JavaScript Promises. It is indeed an essential aspect of the language. Up next, we will learn about Promise APIs. Promise APIs and the async/await keywords make it a much better experience in handling promises. We will learn about it using visual demonstrations and examples.

Until then, please enjoy learning and stay motivated. You can find all the source code used in this article from this Github repo,

GitHub logo atapas / promise-interview-ready

Learn JavaScript Promises in a new way. This repository contains all the source code and examples that make you ready with promises, especially for your interviews 😉.


I hope you enjoyed this article or found it helpful. Let's connect. Please find me on Twitter(@tapasadhikary), sharing thoughts, tips, and code practices. Would you please give a follow?

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .