async/await — Modern Patterns
📖 Concept
async/await is syntactic sugar over Promises that makes asynchronous code look and behave like synchronous code. Introduced in ES2017/Node.js 7.6+, it's now the standard way to write async code in Node.js.
How it works:
asyncmarks a function as returning a Promiseawaitpauses execution until a Promise resolves (or rejects)- The function "yields" to the event loop while waiting — other code continues to run
Key rules:
awaitcan only be used inside anasyncfunction (or at the top level of an ESM module)asyncfunctions always return a Promise- If you return a value, it's wrapped in
Promise.resolve() - If you throw, it becomes
Promise.reject()
Error handling with async/await:
Use try/catch blocks — they work exactly like synchronous error handling:
try {
const data = await fetchData();
} catch (err) {
console.error('Failed:', err);
}
Sequential vs Parallel execution:
// Sequential: 2 seconds total (one after another)
const a = await taskA(); // 1 second
const b = await taskB(); // 1 second
// Parallel: 1 second total (both at once)
const [a, b] = await Promise.all([taskA(), taskB()]);
Common patterns:
- Sequential processing —
for...ofwithawaitfor ordered operations - Parallel processing —
Promise.all()for independent operations - Controlled concurrency — Process N items at a time (batch processing)
- Error boundaries — Wrap logical groups in try/catch
🏠 Real-world analogy: async/await is like a personal assistant. You say "get me coffee, then schedule a meeting." The assistant handles it while you work on other things. You only "pause" when you specifically wait for the result.
💻 Code Example
1// async/await — Modern Patterns23const fs = require("fs").promises;45// 1. Basic async/await6async function loadConfig() {7 try {8 const data = await fs.readFile("config.json", "utf-8");9 return JSON.parse(data);10 } catch (err) {11 if (err.code === "ENOENT") {12 console.log("Config not found, using defaults");13 return { port: 3000, env: "development" };14 }15 throw err; // Re-throw unexpected errors16 }17}1819// 2. Sequential vs Parallel — Critical performance difference!2021// ❌ SLOW: Sequential (total time = sum of all awaits)22async function getDataSequential() {23 console.time("sequential");24 const users = await fetchData("/users"); // 200ms25 const posts = await fetchData("/posts"); // 200ms26 const comments = await fetchData("/comments"); // 200ms27 console.timeEnd("sequential"); // ~600ms28 return { users, posts, comments };29}3031// ✅ FAST: Parallel (total time = longest operation)32async function getDataParallel() {33 console.time("parallel");34 const [users, posts, comments] = await Promise.all([35 fetchData("/users"), // 200ms ─┐36 fetchData("/posts"), // 200ms ─┼─ All start simultaneously37 fetchData("/comments"), // 200ms ─┘38 ]);39 console.timeEnd("parallel"); // ~200ms40 return { users, posts, comments };41}4243// 3. Sequential processing with for...of44async function processFilesSequentially(filePaths) {45 const results = [];46 for (const filePath of filePaths) {47 // Each file processed one at a time (order preserved)48 const content = await fs.readFile(filePath, "utf-8");49 const processed = await transform(content);50 results.push(processed);51 }52 return results;53}5455// 4. Controlled concurrency — Process N items at a time56async function processBatch(items, batchSize, processor) {57 const results = [];58 for (let i = 0; i < items.length; i += batchSize) {59 const batch = items.slice(i, i + batchSize);60 const batchResults = await Promise.all(61 batch.map((item) => processor(item))62 );63 results.push(...batchResults);64 console.log(`Processed ${Math.min(i + batchSize, items.length)}/${items.length}`);65 }66 return results;67}6869// Usage: process 100 items, 10 at a time70// await processBatch(items, 10, async (item) => {71// return await processItem(item);72// });7374// 5. Error handling patterns7576// Pattern A: try/catch per operation77async function withIndividualErrors() {78 let user;79 try {80 user = await fetchUser(1);81 } catch (err) {82 console.error("User fetch failed:", err.message);83 user = getDefaultUser();84 }8586 let orders;87 try {88 orders = await fetchOrders(user.id);89 } catch (err) {90 console.error("Orders fetch failed:", err.message);91 orders = [];92 }9394 return { user, orders };95}9697// Pattern B: Wrapper function (Go-style error handling)98async function to(promise) {99 try {100 const result = await promise;101 return [null, result];102 } catch (err) {103 return [err, null];104 }105}106107// Usage108async function withGoStyleErrors() {109 const [userErr, user] = await to(fetchUser(1));110 if (userErr) return console.error("No user:", userErr.message);111112 const [ordersErr, orders] = await to(fetchOrders(user.id));113 if (ordersErr) return console.error("No orders:", ordersErr.message);114115 return { user, orders };116}117118// 6. IIFE for top-level await (CommonJS)119(async () => {120 try {121 const config = await loadConfig();122 console.log("App started with config:", config);123 } catch (err) {124 console.error("Startup failed:", err);125 process.exit(1);126 }127})();128129// 7. Async iterators (for await...of)130async function* generateNumbers(count) {131 for (let i = 0; i < count; i++) {132 await new Promise((r) => setTimeout(r, 100));133 yield i;134 }135}136137async function consumeAsyncIterator() {138 for await (const num of generateNumbers(5)) {139 console.log("Got number:", num);140 }141}142143// 8. Promise.all with error handling per item144async function fetchAllWithFallbacks(urls) {145 const results = await Promise.allSettled(146 urls.map(async (url) => {147 const response = await fetch(url);148 if (!response.ok) throw new Error(`HTTP ${response.status}`);149 return response.json();150 })151 );152153 return results.map((result, i) => ({154 url: urls[i],155 success: result.status === "fulfilled",156 data: result.status === "fulfilled" ? result.value : null,157 error: result.status === "rejected" ? result.reason.message : null,158 }));159}160161// Mock helpers162function fetchData(endpoint) {163 return new Promise((r) => setTimeout(() => r(`data from ${endpoint}`), 200));164}165function fetchUser(id) { return Promise.resolve({ id, name: "Alice" }); }166function fetchOrders(userId) { return Promise.resolve([{ id: 1 }]); }167function transform(content) { return Promise.resolve(content.toUpperCase()); }168function getDefaultUser() { return { id: 0, name: "Guest" }; }
🏋️ Practice Exercise
Exercises:
- Refactor a callback-based function to use async/await — compare readability
- Write a function that fetches 10 URLs in parallel using
Promise.all()and async/await - Implement controlled concurrency: process 100 items with max 5 concurrent operations
- Create a Go-style
to()error wrapper and use it in a 3-step async flow - Build an async generator that reads a file line by line and yields parsed JSON objects
- Write a function that races a fetch request against a timeout, with proper cleanup of the losing operation
⚠️ Common Mistakes
Using
awaitin series when operations are independent —const a = await x(); const b = await y();is sequential; usePromise.all([x(), y()])for parallelUsing
forEachwithawait—array.forEach(async (item) => { await process(item); })does NOT wait for each item; usefor...offor sequential orPromise.all(array.map(...))for parallelForgetting try/catch around
await— unhandled Promise rejections crash the process; always wrap async code in error handlersMaking every function
asynceven when it doesn't need to be — addingasyncto synchronous functions adds unnecessary Promise wrapping overheadNot understanding that
awaitonly pauses the current function — other parts of the application continue running on the event loop
💼 Interview Questions
🎤 Mock Interview
Mock interview is powered by AI for async/await — Modern Patterns. Login to unlock this feature.