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.
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 finishedWithout await inside try, the rejection would occur after the try block had already moved on.
Build it yourself
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.
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
OfflineReplace 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?
Completion is saved in this browser only. It does not measure mastery.