JavaScript Async and Await

Handle async code cleanly with async functions, await, error handling, and parallel execution.

TL;DR

  1. 01Mark functions async to make them return promises.
  2. 02Use await to pause and unwrap promise values.
  3. 03Wrap awaited code in try/catch for error handling.

Tips

  1. 01Use Promise.all() for independent async operations to run them in parallel and get faster results.
  2. 02Wrap an async IIFE around top-level code in older environments that don't support top-level await directly.

Warnings

  1. 01Awaiting operations sequentially when they are independent is slower than running them together with Promise.all().
  2. 02An unhandled rejection inside an async function without try/catch crashes Node.js processes by default in current versions.

Async Functions

    async keyword

    Adding async to a function changes its return contract to always be a promise.

    async function fetchData() {
      return "data";
    }
    fetchData().then(result => console.log(result));
    Wrapped return value

    An async function that returns a plain value wraps it in a resolved promise.

    await inside async

    You can use await only inside a function declared with async.

    async function getData() {
      const response = await fetch("/api/data");
      return response.json();
    }
    Cleaner syntax

    Async functions are just a cleaner way to work with promises, nothing more.

    Always a promise

    Every async function returns a promise, even one that never uses await.

Await Keyword

    Pause and resolve

    Await pauses the function and hands back the resolved value, nothing more.

    async function getUser(id) {
      const response = await fetch(`/api/users/${id}`);
      const user = await response.json();
      return user;
    }
    Unwraps promises

    Await unwraps the resolved value from a promise automatically.

    Scope restriction

    Await only works inside async functions or at a module's top level.

    async function getMultiple() {
      const [a, b] = await Promise.all([
        fetch("/a").then(r => r.json()),
        fetch("/b").then(r => r.json())
      ]);
      return [a, b];
    }
    Avoid sequential await

    Do not await independent operations one at a time — use Promise.all instead.

Error Handling

    try/catch

    A rejected await throws synchronously, so try/catch works like it does with normal exceptions.

    async function fetchData() {
      try {
        const res = await fetch("/data");
        return await res.json();
      } catch (error) {
        console.error("Failed to fetch:", error);
        return null;
      }
    }
    Catches any await

    The catch block runs when any awaited call inside the try block rejects.

    finally

    Use finally to run cleanup code regardless of success or failure.

    async function withCleanup() {
      try {
        return await operation();
      } finally {
        cleanup();
      }
    }
    Throwing errors

    Throw errors from async functions to propagate them as promise rejections.

    Chained catch

    Attach .catch() to an async call when you prefer promise chaining instead.

    fetchData().then(data => process(data)).catch(error => console.error("Failed:", error.message));

Parallel Execution

    Promise.all()

    Runs every promise concurrently and resolves once all of them succeed.

    async function getUsers() {
      const [user1, user2] = await Promise.all([
        fetch("/users/1").then(r => r.json()),
        fetch("/users/2").then(r => r.json())
      ]);
      return [user1, user2];
    }
    Faster than sequential

    Running promises together is much faster than awaiting each one in turn.

    Promise.race()

    Resolves or rejects as soon as the first promise settles, whichever it is.

    const fastest = await Promise.race([api1(), api2()]);
    Promise.allSettled()

    Waits for every promise to finish and reports each result, even failures.

    Promise.any()

    Resolves as soon as the first promise succeeds, ignoring earlier rejections.

    const result = await Promise.any([
      fetch("/endpoint-1").then(r => r.json()),
      fetch("/endpoint-2").then(r => r.json())
    ]);
    // Resolves with whichever responds first without rejecting

Common Patterns

    Top-level await

    ES modules can await at the top level without a wrapping async function.

    // In a module
    const config = await loadConfig();
    Chained workflow

    Chain multiple async operations together, each awaiting the previous result.

    async function workflow() {
      const data = await fetch1();
      const result = await process(data);
      return await fetch2(result);
    }
    Async IIFE

    Use an async IIFE for immediate async execution without a named function.

    (async () => {
      const data = await fetchData();
      console.log(data);
    })();
    for-await-of

    Loop over async iterables item by item using for-await-of.

    for await (const item of asyncIterator()) {
      console.log(item);
    }

In Practice

FAQ