Async/Await
Objectives
By the end of this lesson, you should be able to:
- Write an
asyncfunction andawaita promise inside it - Handle a rejected promise with
try/catcharoundawait - Explain when sequential
awaitcalls should become parallel ones
💡 Why this matters: Async/await is the standard way modern Node.js and Express code handles asynchronous work, database queries, file reads, external API calls. Every route handler from Module 5 onward that touches anything asynchronous uses this pattern.
⚠️ A note on verification: every snippet and output in this lesson was actually run with Node.js.
async and await
function fetchUser(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id <= 0) reject(new Error("invalid id"));
else resolve({ id, name: "Erin Castillo" });
}, 10);
});
}
async function loadUser(id) {
const user = await fetchUser(id);
console.log("loaded:", user.name);
return user;
}
loadUser(1);
loaded: Erin Castillo
async before a function declaration means it always returns a promise, and unlocks the ability to use await inside it. await fetchUser(id) pauses execution of loadUser until that promise settles, then continues with the resolved value, user, no .then() callback needed. The code reads top to bottom, like synchronous code, even though fetchUser is genuinely asynchronous underneath.
Error Handling with try/catch
async function loadUser(id) {
try {
const user = await fetchUser(id);
console.log("loaded:", user.name);
} catch (err) {
console.log("failed:", err.message);
}
}
loadUser(-1);
failed: invalid id
A rejected promise, when awaited, throws, exactly like throw from Lesson 5. This means ordinary try/catch handles it directly, await fetchUser(-1) throws the rejection’s error, which catch (err) receives, no separate .catch() method needed, this is the same error-handling syntax already familiar from synchronous code.
Sequential vs Parallel await
async function sequential() {
console.time("sequential");
const a = await fetchUser(1);
const b = await fetchUser(2);
console.timeEnd("sequential");
console.log(a.name, b.name);
}
async function parallel() {
console.time("parallel");
const [a, b] = await Promise.all([fetchUser(1), fetchUser(2)]);
console.timeEnd("parallel");
console.log(a.name, b.name);
}
sequential: 20.773ms
User 1 User 2
parallel: 10.447ms
User 1 User 2
sequential() awaits fetchUser(1), then only after that finishes, starts fetchUser(2), roughly doubling the total wait. parallel() starts both calls together with Promise.all() (previous lesson), then awaits them as a group, both fetches happen at the same time, the whole operation takes roughly as long as the slower one alone, not the sum of both. When two async operations don’t depend on each other’s results, running them with Promise.all() instead of separate sequential awaits is a real, measurable improvement, not just a style preference.
Try It
- Write an
asyncfunctiongetProduct(id)that awaits a promise-returningfetchProductfunction and logs the result. - Add a
try/catcharound theawaitin question 1, handling a rejection ifidis invalid. - Write two independent async operations and run them with sequential
awaitcalls, then rewrite them usingPromise.all(), comparing the two. - Explain, in your own words, why
await Promise.all([a, b])is generally faster thanawait a; await b;whenaandbdon’t depend on each other.
Recap
asyncfunctions always return a promise,awaitpauses until a promise settles, letting async code read like synchronous code.- A rejected, awaited promise throws, handled with ordinary
try/catch, no separate.catch()needed. - Independent async operations should run with
Promise.all()rather than sequentialawaitcalls, real time savings, not just style.
Next lesson: this module’s exercises, combining every modern JavaScript feature covered so far.