Async/await is a modern way of writing asynchronous functions in JavaScript. They are built on top of promises and allow us to write asynchronous code in synchronous manners.

Why Asnyc/await?

Promises are great for writing asynchronous code and have solved the famous callback hell problem as well, but they also introduced their own complexities. The code quickly becomes complicated if we chain multiple promises together to perform a complex task.

To reduce the extra boilerplate code around promises, asnyc/await was introduced in ES2017 (ES8). It greatly improves the readability of the code and makes it appears synchronous while working as asynchronous and non-blocking behind the scene.

Async Function

To create an async function, all you need to do is add the async keyword before the function definition:

const sayHi = async () => {
    return 'Hey dev πŸ‘‹';
}

The async keyword before any function always means one (and just one) thing: it returns a promise. Even if it explicitly returns a simple value, like above, the returned value is automatically enclosed inside a resolved promise. Since the value returned is a promise, we can do something like below to get the value:

sayHi().then(greeting => console.log(greeting)); // Hey dev πŸ‘‹

Await

The await keyword can only be used within an async function, otherwise it will throw a syntax error. It waits for a promise to resolve or reject. Here is an example:

const greet = async () => {
    let message = await sayHi();
    console.log(message);
}

greet(); // Hey dev πŸ‘‹

Basically, the await keyword makes JavaScript pauses the function execution until the promise settles, and resumes when the result is available. It is better than promise.then() and makes the code easier to understand and write.

Examples

Here is a quick example of async/await for writing an asynchronous function:

const cage = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('🐦');
        }, 1000);
    });
}

const planet = async () => {
    const bird = await cage();
    console.log('Bird:', bird);
}

planet(); // Bird: 🐦 (after 0.5s)

In the example above, we first declare a function that returns a new promise that resolves to a value after one second. We then create an async function and await for the promise to resolve before logging the returned value to the console.

The power of async/await functions becomes more apparent when multiple asynchronous functions are involved:

const eagle = () => {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve('πŸ¦…');
        }, 200);
    });
}

const duck = () => {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve('πŸ¦†');
        }, 400);
    });
}

const owl = () => {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve('πŸ¦‰');
        }, 600);
    });
}

const planet = async () => {
    const e = await eagle();
    const d = await duck();
    const o = await owl();

    console.log(`Birds: ${e} ${d} ${o}`);
}

planet(); // Birds: πŸ¦… πŸ¦† πŸ¦‰

In the above example, each async function is called sequentially, where every promise is waiting for the promise before (if available) to either resolve or reject before continuing. If you want to execute these promises in parallel, you can simply use Promise.all() to wait until all of them are ready:

// ...
const planet = async () => {
    const [e, d, o] = await Promise.all([eagle(), duck(), owl()]);
    
    console.log(`Birds: ${e} ${d} ${o}`);
}

planet(); // Birds: πŸ¦… πŸ¦† πŸ¦‰

Error Handling

Another benefit of using async/await functions is that both asynchronous and synchronous errors are handled in the same construct, using good old try...catch statements. All you need to do is, put your code in a try block and the catch block will automatically capture all errors:

const isEven = (num) => {
    return new Promise((resolve, reject) => {
        if (num % 2 == 0) {
            resolve('πŸ‘Œ');
        } else {
            reject('πŸ’€');
        }
    });
}

const verify = async (num) => {
    try {
        const sign = await isEven(num);
        console.log(sign);
    } catch (err) {
        console.log(err);
    }
}

verify(8); // πŸ‘Œ
verify(15); // πŸ’€
verify('abc'); // πŸ’€

This synchronous error handling doesn't just work for promises, but it also works when there is an actual syntax or runtime error as you can see above for verify('abc').

Don't want to use try...catch?

Since the async function returns a promise, you can also use the catch() method to deal with unhandled errors:

// ...
const verify = async (num) => {
    const sign = await isEven(num);
    console.log(sign);
}

verify('hey').catch(err => console.log(err)); // πŸ’€

Summary

Async/await is a new way of writing asynchronous code in JavaScript. Before this, we used callbacks and promises for asynchronous code. It allows us to write synchronous-looking code that is easier to maintain and understand.

  • Async/await is non-blocking, built on top of promises and can't be used in plain callbacks.
  • The async function always returns a promises.
  • The await keyword is used to wait for the promise to settle. It can only be used inside an async function.
  • A try...catch block can be used to capture both asynchronous and synchronous (syntax, runtime) errors inside an async function.

πŸ™‹β€β™‚οΈ Like this article? Follow @attacomsian on Twitter. You can also follow me on LinkedIn and DEV.


Need help to start a new Spring Boot or MEAN stack project? I am available for contract work. Hire me to accomplish your business goals with engineering and design. Let’s talk about your project: hi@attacomsian.com.