JavaScript async and await - in plain English, please

JavaScript async and await - in plain English, please

Async and Await in JavaScript is the most reasonable way to handle promises and asynchronous operations. But, there is more to it. Let's explore.

ยท

7 min read

Featured on daily.dev

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 is 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 enhancing 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']};
    }
    

    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']};
    }
    

    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)
    

    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));
    

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();
    
  • 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';
    }
    

    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.
    

    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();
    })();
    

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);
    });
}

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();

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);
    };
}

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,


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? You can hit the Subscribe button at the top of the page to get an email notification on my latest posts.

Did you find this article valuable?

Support Tapas Adhikary by becoming a sponsor. Any amount is appreciated!