learn.colinkim.dev

async / await

Learn how async and await make asynchronous code read like synchronous code, and how to handle errors, parallel work, and sequential flows.

async/await is syntactic sugar over promises. It does not add new capabilities — it makes existing patterns easier to read and write.

The async keyword

async before a function means the function always returns a promise:

async function getUser() {
  return { name: "Ada" };
}

getUser().then((user) => console.log(user));  // { name: "Ada" }

Even though the function returns a plain object, async wraps it in a resolved promise automatically.

If the function throws, the returned promise rejects:

async function getUser() {
  throw new Error("Not found");
}

getUser().catch((error) => console.error(error.message));  // "Not found"

The await keyword

await pauses the execution of an async function until a promise settles:

async function displayUser() {
  const user = await fetchUser(1);
  console.log(user);
}

await can only be used inside async functions (or at the top level of modules — covered below). It makes async code read top to bottom:

// With promises
function displayUser() {
  fetchUser(1)
    .then((user) => fetchOrders(user.id))
    .then((orders) => console.log(orders))
    .catch((error) => console.error(error));
}

// With async/await
async function displayUser() {
  try {
    const user = await fetchUser(1);
    const orders = await fetchOrders(user.id);
    console.log(orders);
  } catch (error) {
    console.error(error);
  }
}

The async/await version reads like synchronous code. Each line waits for the previous one to complete.

Error handling with try/catch

await throws a rejected promise as a regular error. Catch it with try/catch:

async function displayUser() {
  try {
    const user = await fetchUser(-1);
    console.log(user);
  } catch (error) {
    console.error("Failed to load user:", error.message);
  }
}

You can catch errors at different levels — wrap individual operations or the entire function:

async function loadData() {
  let user;

  try {
    user = await fetchUser(1);
  } catch (error) {
    console.error("User failed, using default");
    user = { name: "Anonymous" };
  }

  const orders = await fetchOrders(user.id);
  return { user, orders };
}

Sequential vs parallel async work

Sequential (one after another)

When each operation depends on the previous one, await sequentially:

async function getUserOrders(userId) {
  const user = await fetchUser(userId);
  const orders = await fetchOrders(user.id);
  return orders;
}

This waits for the user before fetching orders. Total time = time for user + time for orders.

Parallel (at the same time)

When operations are independent, use Promise.all with await:

async function getDashboardData() {
  const [user, orders, products] = await Promise.all([
    fetchUser(1),
    fetchOrders(1),
    fetchProducts(),
  ]);

  return { user, orders, products };
}

This runs all three requests simultaneously. Total time = the slowest of the three, not the sum.

// Slow — sequential
async function loadUsersSequential(ids) {
  const users = [];
  for (const id of ids) {
    const user = await fetchUser(id);
    users.push(user);
  }
  return users;
}

// Fast — parallel
async function loadUsersParallel(ids) {
  const promises = ids.map((id) => fetchUser(id));
  return await Promise.all(promises);
}

Top-level await

In ES modules, you can use await at the top level without wrapping it in an async function:

// config.js
const config = await fetch("/api/config").then((r) => r.json());

export default config;

Any module that imports from config.js will wait for the promise to resolve before its code runs. Top-level await is available in all modern browsers and Node.js.

Iterating over async iterables with for await...of

When working with async iterables or async generators, use for await...of:

async function* readChunks(stream) {
  // yields chunks of data as they arrive
}

for await (const chunk of readChunks(stream)) {
  process(chunk);
}

This processes each yielded value as it becomes available, rather than waiting for all results. Use this when you need to stream results incrementally rather than collect them all at once.

await with non-promise values

await works on any value. Non-promise values are wrapped in a resolved promise automatically:

async function example() {
  const value = await 42;
  console.log(value);  // 42
}

This is rarely useful directly but means you can await a function that might return a promise or a plain value without needing to check.

Array.fromAsync() for async iterables (ES2025)

When working with async iterables or async generators, Array.fromAsync() collects the results into an array:

async function* fetchIds(ids) {
  for (const id of ids) {
    yield fetch(`/api/users/${id}`).then((r) => r.json());
  }
}

const users = await Array.fromAsync(fetchIds([1, 2, 3]));

This is the async equivalent of Array.from(). It handles async iterables, async generators, and even regular iterables of promises.

What to carry forward

  • async functions always return promises
  • await pauses execution until a promise settles — only usable inside async functions or at module top level
  • use try/catch to handle errors from awaited promises
  • sequential await is appropriate when operations depend on each other
  • use Promise.all with await for independent parallel operations
  • avoid awaiting in a loop for independent items — use Promise.all instead
  • top-level await lets modules wait for async initialization before exporting
  • Array.fromAsync() collects async iterables or generators into arrays (ES2025)

async/await is the dominant pattern for asynchronous JavaScript. Combined with the event loop understanding from the first lesson, it explains how async code behaves. The next lesson covers fetching data from APIs in practice.

Quick Check

One answer

What does await pause?

Choose the best answer and use it to track your progress through the lesson.

Progress

Quick checks

No quick checks in this lesson.

Mark lesson manually or answer quick checks to track progress.