Skip to main content

Command Palette

Search for a command to run...

Understanding Async/Await in JavaScript

Updated
4 min read

As JavaScript applications became more complex, handling asynchronous operations using callbacks started to become difficult to manage. This led to deeply nested code structures, often referred to as callback hell.

Promises improved this situation, but chaining multiple .then() calls could still make code harder to read. To simplify asynchronous programming further, JavaScript introduced async/await.

Async/await provides a cleaner and more readable way to write asynchronous code while still using the power of promises.

In this article, we will cover:

  • Why async/await was introduced

  • How async functions work

  • The concept of the await keyword

  • Error handling with async code

  • Comparison with promises


Why Async/Await Was Introduced

Before async/await, asynchronous code was commonly written using callbacks.

Example:

getUser(function(user) {
  getOrders(user.id, function(orders) {
    getOrderDetails(orders[0], function(details) {
      console.log(details);
    });
  });
});

This nested structure becomes difficult to read and maintain.

Promises improved this by allowing chaining:

getUser()
  .then(user => getOrders(user.id))
  .then(orders => getOrderDetails(orders[0]))
  .then(details => console.log(details))
  .catch(error => console.error(error));

While promises are better, async/await was introduced to make asynchronous code look and behave more like synchronous code, improving readability.


Async/Await as Syntactic Sugar

Async/await is often described as syntactic sugar over promises.

This means:

  • It does not replace promises

  • It simply provides a cleaner syntax for working with them

Behind the scenes, async functions still return promises.


How Async Functions Work

An async function is declared using the async keyword.

Example:

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

Even though the function returns a string, JavaScript automatically wraps it in a promise.

Example:

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

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

Output:

Hello

The await Keyword

The await keyword is used inside async functions to pause execution until a promise resolves.

Example:

function fetchData() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve("Data received");
    }, 2000);
  });
}

async function getData() {
  const result = await fetchData();
  console.log(result);
}

getData();

Explanation:

  1. fetchData() returns a promise

  2. await pauses the function until the promise resolves

  3. The resolved value is stored in result

This makes asynchronous code look much simpler and easier to understand.


Improving Readability with Async/Await

Let us compare promise-based code with async/await.

Using Promises

function fetchUser() {
  return Promise.resolve("User data");
}

fetchUser()
  .then(data => {
    console.log(data);
  })
  .catch(error => {
    console.error(error);
  });

Using Async/Await

async function getUser() {
  try {
    const data = await fetchUser();
    console.log(data);
  } catch (error) {
    console.error(error);
  }
}

getUser();

The async/await version is cleaner and easier to read, especially when multiple asynchronous operations are involved.


Error Handling with Async Code

Error handling with async/await uses the familiar try...catch structure.

Example:

async function fetchData() {
  try {
    const response = await Promise.reject("Something went wrong");
    console.log(response);
  } catch (error) {
    console.log("Error:", error);
  }
}

fetchData();

This approach keeps error handling structured and readable.


Comparison with Promises

Feature Promises Async/Await
Syntax style .then() and .catch() Looks like synchronous code
Readability Can become complex with chaining Easier to read
Error handling .catch() try...catch
Underlying behavior Promise-based Built on top of promises

Async/await improves code readability but still relies on promises internally.


Simple Real-World Example

Fetching data from an API often involves asynchronous code.

Example:

async function loadData() {
  const response = await fetch("https://api.example.com/data");
  const data = await response.json();

  console.log(data);
}

loadData();

Here, await ensures that each step completes before moving to the next.


Conclusion

Async/await is a powerful feature that simplifies asynchronous programming in JavaScript. By providing a cleaner syntax over promises, it allows developers to write code that is easier to read, maintain, and debug.

Key points to remember:

  • Async/await was introduced to simplify asynchronous code

  • async functions always return promises

  • await pauses execution until a promise resolves

  • Error handling works naturally with try...catch

  • Async/await improves readability while still relying on promises internally

Understanding async/await will help you write more maintainable JavaScript and manage asynchronous workflows more effectively.