Explaining Async/Await in JavaScript in 10 Minutes


Introduction

  • For a long time, we've relied on callbacks to deal with asynchronous code in JavaScript. As a result, many of us have faced dreadful experiences dealing with deeply nested functions (callback hell).
Callback hell
  • Callbacks come with a lot of drawbacks. When we have multiple asynchronous operations, callbacks have to wait for each other to execute, prolonging the completion time. Additionally, writing nested callbacks makes our code messy and hard to maintain.
  • In the ES6 (ECMAScript 2015) version, JavaScript introduced Promises. This was a fantastic replacement for callbacks and was quickly embraced by the community. Our new code looks similar to the old one, making it easier to follow and maintain. However, the issues with callbacks weren't completely resolved.
  • Since the ES7 (ECMAScript 2016) version, Async/Await has been added to make asynchronous code in JavaScript look and feel easier to use.

What is Async/Await?

Async/Await is a feature of JavaScript that helps us handle asynchronous functions synchronously. It's built on top of Promises and is compatible with Promise. Here's how it works:


Async - used when declaring an asynchronous function.

async function asyncFunction() {
// block code
// could be return anything (or not)
}

In an async function, when you return any value, it will always be a Promise.

For example:

  • Returning a number will result in Promise<number>.
  • If nothing is returned, the result will be Promise<void>.


Await - pauses the execution of async functions.

const result = await asyncFunction() // must fulfilled to continue
// do anything else

  • When placed before a Promise, it waits until the Promise is executed and returns the result before continuing to execute the next line of code.
  • Await only works with Promises; it doesn't work with callbacks.
  • Await can only be used inside async functions.


Example Usage

Here, I'll provide a simple example, often used, of writing a function to make an HTTP GET call and receive JSON data. I'll implement it in two ways for you to compare between using Promise and Async/Await.

// Promise approach
function getData() {
return new Promise((resolve) => {
fetch('api endpoint').then(response => resolve(response.json()));
});
}

// Async/Await approach
async function getData() {
const response = await fetch('api endpoint');
return response.json();
}

// then use function like
const data = await getData();
console.log(data);

Both functions above perform the same function - they both return a Promise containing JSON data. However, you can see that when using Async/Await, the code is more concise and easier to read.


Comparing Async/Await and Promise:

  • In essence, when using Async/Await, we are actually interacting with Promises.
  • However, Promises provide some built-in functions that allow us to manipulate multiple Promises simultaneously more easily, such as Promise.all, Promise.race, etc.
  • Using Async/Await is suitable when you want to implement asynchronous code in a synchronous-looking manner for readability and understanding, suitable for sequential deployment.

For example:

async function sum() {
// sequentially
let value1 = await getValue1();
let value2 = await getValue2();
let value3 = await getValue3();
return value1 + value2 + value3;
}

async function sum() {
// send all requests at the same time.
const results = await Promise.all([getValue1, getValue2, getValue3]); // result is Promise array
return results.reduce((total, value) => total + value);
}

Error Handling

Async/Await allows us to catch unexpected errors simply by using try/catch/finally as usual. Both synchronous and asynchronous errors can be caught:

async function asyncFunction() {
try {
const result = await asyncHandle();
// handle result
} catch(error) {
console.error(error)
} finally {
// optional, always execute here
}
}

Another way is to use the catch() function (a built-in function of Promise), because async functions all return Promises, so error catching would look like this:

async function asyncFunction() {
const result = await asyncHandle();
return result;
}

asyncFunction()
.then(successHandler)
.catch(errorHandler)
.finally(finallyHandler);

Conclusion

  • Using Async/Await helps us implement asynchronous code in a synchronous manner, making the syntax easier to read and understand, leading to easier maintenance.
  • Currently, Async/Await is supported on most modern browsers (except IE11), it's supported in NodeJS environment, and it's even available in TypeScript. So, whether you're developing on any JavaScript environment, you can make use of it.

Feel free to like and share the content if you find it helpful, and don't hesitate to leave a comment to give feedback or if you have any questions you'd like to discuss.

Comments

Popular posts from this blog

A Handy Guide to Using Dynamic Import in JavaScript

What is react-query? Why should we use react-query?

Using styled-components in React/Next Applications

Enhance Security for Node.js Applications

Understanding React Server Component

Constants, Object.freeze, Object.seal and Immutable in JavaScript

Installing PostgreSQL with Docker

Sitemap

Using PureComponent and React.memo to Improve Performance in React