Skip to main content

Command Palette

Search for a command to run...

Async/Await in JavaScript: Writing Cleaner Asynchronous Code

Updated
7 min read
Async/Await in JavaScript: Writing Cleaner Asynchronous Code

How async/await improves readability compared to callbacks and promises.

JavaScript applications often need to perform tasks that take time to complete. For example, fetching data from an API, reading files, or waiting for timers are all operations that do not finish instantly. These types of tasks are known as asynchronous operations.

Earlier, JavaScript developers used callbacks to handle asynchronous code. Later, Promises were introduced to improve the readability and structure of asynchronous programming. However, promise chains can still become complex when many asynchronous steps are involved.

To make asynchronous code even easier to read and write, JavaScript introduced Async/Await.

Async/Await allows developers to write asynchronous code in a way that looks almost like normal synchronous code. This makes programs easier to understand and maintain.

In this article, we will explore:

  • Why async/await was introduced

  • How async functions work

  • The concept of the await keyword

  • Error handling in async code

  • Comparison between promises and async/await

By the end of this article, you will understand how async/await simplifies asynchronous JavaScript programming.


Why Async/Await Was Introduced

Before async/await existed, developers used callbacks to handle asynchronous tasks. While callbacks work well for simple operations, they often create deeply nested code when multiple tasks depend on each other.

Example of Callback Nesting

setTimeout(() => {
  console.log("Step 1 completed");

  setTimeout(() => {
    console.log("Step 2 completed");

    setTimeout(() => {
      console.log("Step 3 completed");
    }, 1000);

  }, 1000);

}, 1000);

This structure becomes hard to read as more tasks are added.

Promises improved the situation by allowing developers to chain asynchronous operations.

Example with Promises

fetch("https://jsonplaceholder.typicode.com/posts/1")
  .then(response => response.json())
  .then(data => {
    console.log(data);
  })
  .catch(error => {
    console.log(error);
  });

This approach is cleaner than callbacks, but long promise chains can still be difficult to follow.

Async/Await was introduced to make asynchronous code look more like synchronous code, improving readability and reducing complexity.


What is Async/Await?

Async/Await is a modern JavaScript feature used to handle asynchronous operations in a cleaner and more readable way.

It is built on top of Promises and works by using two keywords:

  • async

  • await

These keywords allow asynchronous code to be written in a style that looks similar to regular synchronous code.


How Async Functions Work

An async function is a function declared using the async keyword. When a function is marked as async, it always returns a promise.

Example

async function greet() {
  return "Hello World";
}

greet().then(message => console.log(message));

Output

Hello World

Even though the function returns a string, JavaScript automatically wraps the return value inside a promise.

This means async functions always behave like promises.


Using Await Inside Async Functions

The await keyword is used inside an async function to pause the execution of the function until a promise is resolved.

This allows asynchronous code to appear more like normal step-by-step code.

Example

function waitTwoSeconds() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve("Finished waiting");
    }, 2000);
  });
}

async function runTask() {
  const result = await waitTwoSeconds();
  console.log(result);
}

runTask();

Output

Finished waiting

Here is what happens:

  1. waitTwoSeconds() returns a promise.

  2. The await keyword pauses execution until the promise resolves.

  3. Once resolved, the result is stored in the variable result.

This makes the code look very similar to synchronous programming.


Async Function Execution Flow

Async Function Starts
        |
        v
Encounter await
        |
        v
Wait for Promise
        |
        v
Promise Resolved
        |
        v
Continue Execution

This flow makes asynchronous code easier to understand.


Example: Fetching API Data with Async/Await

Let’s see a practical example using the fetch API.

Promise Version

fetch("https://jsonplaceholder.typicode.com/posts/1")
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.log(error));

Async/Await Version

async function getPost() {
  try {
    const response = await fetch("https://jsonplaceholder.typicode.com/posts/1");
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.log(error);
  }
}

getPost();

The async/await version looks much cleaner and easier to follow.


Error Handling with Async/Await

Handling errors is an important part of asynchronous programming. With promises, errors are handled using .catch().

With async/await, we can use try...catch blocks.

Example

async function getUser() {
  try {
    const response = await fetch("https://jsonplaceholder.typicode.com/users/1");
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.log("Error occurred:", error);
  }
}

getUser();

If the request fails, the error will be caught in the catch block.

Using try/catch makes error handling easier and more readable.


Async/Await as Syntactic Sugar

Async/Await is often described as syntactic sugar for promises.

This means it does not replace promises internally. Instead, it provides a simpler syntax to work with them.

Under the hood, async/await still uses promises.

Example:

async function example() {
  return "Hello";
}

Is similar to:

function example() {
  return Promise.resolve("Hello");
}

Async/await simply hides the complexity of promises and makes the code easier to read.


Promise vs Async/Await Comparison

Promise Style

function getData() {
  fetch("https://jsonplaceholder.typicode.com/posts/1")
    .then(response => response.json())
    .then(data => console.log(data))
    .catch(error => console.log(error));
}

Async/Await Style

async function getData() {
  try {
    const response = await fetch("https://jsonplaceholder.typicode.com/posts/1");
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.log(error);
  }
}

Async/Await improves readability by making asynchronous code look sequential.


Promise vs Async/Await Flow Diagram

Promise Flow

Start
  |
  v
Promise Created
  |
  v
.then()
  |
  v
.then()
  |
  v
.catch()

Async/Await Flow

Start
  |
  v
Async Function
  |
  v
await Promise
  |
  v
Result Returned
  |
  v
Continue Execution

The async/await version is much easier to follow.


Benefits of Async/Await

Async/Await provides several advantages for developers.

Improved Readability

Code written with async/await looks more like normal sequential code.

Better Error Handling

Using try/catch blocks makes error handling clearer.

Cleaner Code

Async/await reduces the need for long .then() chains.

Easier Debugging

Debugging async/await code is often simpler than debugging promise chains.


Important Rules of Async/Await

There are a few important rules when using async/await.

  1. await can only be used inside an async function.

  2. Async functions always return a promise.

  3. Await pauses execution until the promise resolves.

  4. Errors should be handled using try/catch.


Real-World Uses of Async/Await

Async/Await is widely used in modern JavaScript applications.

Common uses include:

  • Fetching API data

  • Database queries

  • File operations

  • Network requests

  • Server-side development with Node.js

Most modern frameworks such as React, Next.js, and Node.js use async/await extensively.


Conclusion

Async/Await is one of the most important features in modern JavaScript. It simplifies asynchronous programming by making code easier to read and write.

In this article, we explored:

  • Why async/await was introduced

  • How async functions work

  • The purpose of the await keyword

  • Handling errors using try/catch

  • The relationship between promises and async/await

Async/Await allows developers to write asynchronous code that looks like synchronous code while still benefiting from the power of promises.

Understanding async/await is essential for building modern JavaScript applications, as it helps developers write cleaner, more readable, and maintainable asynchronous code.