JavaScript Async and Await

The async and await keywords provide a cleaner way to work with Promises. They make asynchronous code easier to read by allowing Promise-based operations to be written in a style that looks similar to synchronous code.

An async function always returns a Promise, while await pauses execution inside that async function until the awaited Promise settles.

Creating an Async Function

Add the async keyword before a function declaration to make it asynchronous.

JavaScript
Creating a basic async function.
async function greet() {
  return "Hello JavaScript";
}

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

Even though the function returns a normal string, the async keyword automatically wraps that value in a resolved Promise.

Using await

The await keyword waits for a Promise to settle and returns its fulfilled value. It can normally be used inside an async function.

JavaScript
Waiting for a Promise result.
function getData() {
  return Promise.resolve("Data loaded");
}

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

loadData();

Using await avoids manually creating long then() chains and can make sequential asynchronous operations easier to understand.

Handling Errors with try and catch

Rejected Promises can be handled with try...catch when using await. This provides a familiar structure for handling asynchronous errors.

JavaScript
Handling an asynchronous error.
async function loadUser() {
  try {
    const response = await fetch("https://api.example.com/user");
    const user = await response.json();

    console.log(user);
  } catch (error) {
    console.error("Request failed:", error.message);
  }
}

loadUser();

Keeping the await operations inside try allows errors from rejected Promises to be handled in one place.

Running Operations Sequentially

When one operation depends on the result of another, await can be used to execute them in sequence.

JavaScript
Running dependent asynchronous operations.
async function loadUserOrders() {
  const user = await getUser();
  const orders = await getOrders(user.id);

  console.log(orders);
}

The second operation starts after the first one has completed. This is appropriate when the second operation needs data produced by the first.

Running Independent Operations in Parallel

If asynchronous operations are independent, awaiting them one after another can unnecessarily increase the total time. Promise.all() can run them together.

JavaScript
Running independent requests together.
async function loadDashboard() {
  const [users, products, orders] = await Promise.all([
    getUsers(),
    getProducts(),
    getOrders()
  ]);

  console.log(users);
  console.log(products);
  console.log(orders);
}

This pattern is useful for dashboards and API requests where several pieces of data can be loaded independently.

Using async and await with Fetch

The Fetch API returns a Promise, making it a common use case for async and await.

JavaScript
Fetching API data with async and await.
async function getUsers() {
  const response = await fetch("https://api.example.com/users");

  if (!response.ok) {
    throw new Error("Failed to fetch users");
  }

  const users = await response.json();
  return users;
}

getUsers()
  .then((users) => console.log(users))
  .catch((error) => console.error(error.message));

The response itself must be converted to JSON asynchronously, so response.json() is also awaited.

Awaiting Normal Values

await can also be used with ordinary values. JavaScript treats the value as an already fulfilled Promise.

JavaScript
Using await with a normal value.
async function example() {
  const value = await 100;
  console.log(value);
}

example();

Avoid Unnecessary Sequential Awaits

A common performance mistake is awaiting independent operations one by one. If the operations do not depend on each other, start them together with Promise.all().

JavaScript
Using Promise.all for independent operations.
async function loadData() {
  const [users, posts] = await Promise.all([
    getUsers(),
    getPosts()
  ]);

  console.log(users, posts);
}

Top-Level await

Modern JavaScript modules can use await at the top level without wrapping the code inside an async function.

JavaScript
Using top-level await in a JavaScript module.
const response = await fetch("https://api.example.com/data");
const data = await response.json();

console.log(data);

Top-level await is supported in JavaScript modules and is useful when module initialization depends on asynchronous data.

Async and Await Best Practices

Use async functions when working with Promise-based operations. Handle expected failures with try...catch, avoid blocking independent operations unnecessarily, and use Promise.all() when multiple asynchronous tasks can run concurrently.

Keep asynchronous functions focused on a clear task. Returning useful values from async functions also makes them easier to reuse and test.

Summary

async and await make Promise-based JavaScript easier to read and maintain. An async function always returns a Promise, while await retrieves the result of a Promise inside an asynchronous function.

Use sequential await when operations depend on each other, and Promise.all() when independent operations can run at the same time. Combined with try...catch, async and await provide a clean foundation for handling asynchronous JavaScript applications.