async / await

The Course
•
4 min read

An async function always returns a promise

Put async before a function definition when it needs await. Returning a value fulfills its returned promise; throwing rejects it. Even async function count() { return 2; } returns a promise, not a plain 2. Await extracts the fulfillment value or throws the rejection reason inside the awaiting function.

Await pauses that function’s continuation. It does not block the browser’s event loop, freeze other event handlers, or make unrelated work wait. Values that are not promises can also be awaited, but that rarely needs to be a design choice.

Use try/catch around an awaited operation when you can choose a useful response. A catch that logs and returns nothing turns failure into a fulfilled undefined result; that can hide the problem from its caller. Decide whether to recover with a documented value or rethrow. A finally block runs after success or failure and is useful for releasing resources, not for silently replacing the result.

Each example supplies its own local asynchronous function. No network request is involved yet. This lets you learn control flow before adding response status codes and remote data.

A consistent entry point for the examples

From here onward, async examples use async function main() { ... } followed by main();. The first line defines the program’s async entry; the final call starts it and returns its promise. The editor waits for that promise, so it can capture the complete result. Keep main() as the last statement when editing; put extra program steps inside main.

Inside main, await can extract each result and try/catch can handle rejection. An inner function such as loadTitle is an ordinary helper; putting it inside main keeps the example self-contained. You can later move helpers into modules without changing what they return. This explicit entry point also works in a classic webpage script; you do not need top-level await for these exercises.

Predict and trace

Write the output before running. Follow the values through each call; if your prediction differs, identify the first step that surprised you.

Predict, then run JavaScript
async function main() {
  async function loadTitle() {
    return "Notes";
  }

  async function fail() {
    throw new Error("Unavailable");
  }

  console.log(await loadTitle());

  try {
    await fail();
  } catch (error) {
    console.log(error.message);
  } finally {
    console.log("Attempt finished");
  }
}

main();
Check your prediction
Notes
Unavailable
Attempt finished

Without await inside try, the rejection would occur after the try block had already moved on.

Build it yourself

Exercise

async / await: practise

Write async label(load), which awaits a loader function and returns “Book: ” followed by its title. Let failures reach the caller. Print a success and a caught rejection.

Your working space JavaScript
async function main() {
  // Call load(), await it, and return a string.
}

main();
Hint 1: the idea

The function receives an operation, not a promise created elsewhere.

Hint 2: a first step

An uncaught throw in async label rejects label’s returned promise.

Show one solution
async function main() {
  async function label(load) {
    const title = await load();

    return `Book: ${title}`;
  }

  console.log(await label(async () => "The Web"));

  try {
    await label(async () => {
      throw new Error("Offline");
    });
  } catch (error) {
    console.log(error.message);
  }
}

main();
Book: The Web
Offline

Replace the loader with one returning an empty title. An async success is not automatically a valid domain value; decide which layer should reject it.

Test a changed requirement

Replace the loader with one returning an empty title. An async success is not automatically a valid domain value; decide which layer should reject it.

Explain before moving on

Why must the caller await label? What changes if you catch and return undefined instead of propagating failure?

Was this article helpful?

Your response is saved on this device.