async/await
Objectives
By the end of this chapter, you should be able to:
- Mark a function
asyncand useawaitinside it - Explain what an
asyncfunction actually returns - Rewrite a promise chain using
awaitinstead of.then() - Explain the difference between awaiting promises one at a time and running them together
💡 Why this matters:
async/awaitis how most real JavaScript code works with promises today. It doesn’t replace promises, it’s built directly on top of them, but it reads far closer to ordinary, top-to-bottom code.
The async Keyword
Adding async before a function definition changes two things: you can use await inside it, and the function automatically returns a promise, even if you return a plain value.
async function getFive() {
return 5;
}
getFive().then((value) => {
console.log(value);
});
// 5
getFive() looks like it returns 5 directly, but calling it actually gives you a promise that resolves to 5. That’s what makes .then() work on the result.
The await Keyword
Inside an async function, await pauses that function until the promise on its right resolves, then hands you the resolved value directly, no .then() needed.
function delayedValue(value, ms) {
return new Promise((resolve) => setTimeout(() => resolve(value), ms));
}
async function getUser() {
const user = await delayedValue({ name: "Sam" }, 1000);
console.log("Got user:", user.name);
}
getUser();
// (one second later) Got user: Sam
Nothing else on the page freezes while await waits, only the code inside getUser() pauses at that line. This is the same non-blocking behavior from the Timers lesson, await just makes it look like ordinary sequential code.
await only works inside a function marked async. Using it anywhere else is a syntax error.
Rewriting a Promise Chain
Compare this to the .then() chain from last lesson, doing the exact same three sequential steps:
async function runSteps() {
const r1 = await delayedValue("Step 1 done", 1000);
const r2 = await delayedValue(r1 + " -> Step 2 done", 1000);
const r3 = await delayedValue(r2 + " -> Step 3 done", 1000);
console.log("Chain result:", r3);
}
runSteps();
// (three seconds later) Chain result: Step 1 done -> Step 2 done -> Step 3 done
Same behavior, same timing, but each step reads as a plain line of code, assign a variable, use it on the next line. No .then(), no nested or nesting-adjacent callbacks.
Sequential vs. Parallel: A Real Gotcha
Awaiting promises one after another runs them one after another, even if they don’t depend on each other.
async function sequential() {
const start = Date.now();
await delayedValue("a", 1000);
await delayedValue("b", 1000);
console.log("took about", Date.now() - start, "ms");
}
sequential();
// took about 2000 ms
Each await waits for the previous line to finish before starting the next one, even though "a" and "b" have nothing to do with each other. Since they’re independent, they could run at the same time instead. Combine await with Promise.all() (previous lesson) to do that:
async function parallel() {
const start = Date.now();
await Promise.all([delayedValue("a", 1000), delayedValue("b", 1000)]);
console.log("took about", Date.now() - start, "ms");
}
parallel();
// took about 1000 ms
Both versions get the same two results. The sequential version takes roughly the sum of both delays, the parallel version takes roughly the longer of the two, since both start at the same time. Reach for Promise.all() whenever you’re awaiting several promises that don’t depend on each other’s results.
A First Look at Error Handling
await on a rejected promise throws, which means ordinary try/catch (Course 1, Error Handling) works with it directly.
function delayedFailure(ms) {
return new Promise((_, reject) => setTimeout(() => reject(new Error("failed")), ms));
}
async function tryIt() {
try {
await delayedFailure(1000);
} catch (error) {
console.log("Caught:", error.message);
}
}
tryIt();
// (one second later) Caught: failed
This is covered in full next lesson, for now, know that try/catch is the standard way to handle a rejected await inside an async function.
Try It
Starter code for all four exercises:
function delayedValue(value, ms) {
return new Promise((resolve) => setTimeout(() => resolve(value), ms));
}
- Write an
asyncfunction that awaitsdelayedValue("done", 500)and logs the result. - Write an
asyncfunction with three sequentialawaitcalls todelayedValue(), each building on the previous result, matching the chain example above but with your own values. - Write two
asyncfunctions, one that awaits twodelayedValue()calls one at a time, and one that awaits both together withPromise.all(). Log how long each takes and compare. - Write an
asyncfunction that calls a promise-returning function which always rejects, and handle the rejection withtry/catch.
Recap
asyncbefore a function makes it always return a promise, and allowsawaitinside it.awaitpauses anasyncfunction until a promise resolves, then gives you the value directly, no.then()required.- Awaiting promises one at a time runs them sequentially, even if they’re independent.
Promise.all()combined withawaitruns independent promises together, finishing sooner. try/catchhandles a rejectedawaitthe same way it handles a thrown error.
Next lesson: the Fetch API, using everything from this module to actually request data from a server.