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:

  • async marks a function as returning a Promise
  • await pauses execution until a Promise resolves (or rejects)
  • The function "yields" to the event loop while waiting — other code continues to run

Key rules:

  1. await can only be used inside an async function (or at the top level of an ESM module)
  2. async functions always return a Promise
  3. If you return a value, it's wrapped in Promise.resolve()
  4. 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 processingfor...of with await for ordered operations
  • Parallel processingPromise.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

codeTap to expand ⛶
1// async/await — Modern Patterns
2
3const fs = require("fs").promises;
4
5// 1. Basic async/await
6async 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 errors
16 }
17}
18
19// 2. Sequential vs Parallel — Critical performance difference!
20
21// ❌ SLOW: Sequential (total time = sum of all awaits)
22async function getDataSequential() {
23 console.time("sequential");
24 const users = await fetchData("/users"); // 200ms
25 const posts = await fetchData("/posts"); // 200ms
26 const comments = await fetchData("/comments"); // 200ms
27 console.timeEnd("sequential"); // ~600ms
28 return { users, posts, comments };
29}
30
31// ✅ 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 simultaneously
37 fetchData("/comments"), // 200ms ─┘
38 ]);
39 console.timeEnd("parallel"); // ~200ms
40 return { users, posts, comments };
41}
42
43// 3. Sequential processing with for...of
44async 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}
54
55// 4. Controlled concurrency — Process N items at a time
56async 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}
68
69// Usage: process 100 items, 10 at a time
70// await processBatch(items, 10, async (item) => {
71// return await processItem(item);
72// });
73
74// 5. Error handling patterns
75
76// Pattern A: try/catch per operation
77async 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 }
85
86 let orders;
87 try {
88 orders = await fetchOrders(user.id);
89 } catch (err) {
90 console.error("Orders fetch failed:", err.message);
91 orders = [];
92 }
93
94 return { user, orders };
95}
96
97// 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}
106
107// Usage
108async function withGoStyleErrors() {
109 const [userErr, user] = await to(fetchUser(1));
110 if (userErr) return console.error("No user:", userErr.message);
111
112 const [ordersErr, orders] = await to(fetchOrders(user.id));
113 if (ordersErr) return console.error("No orders:", ordersErr.message);
114
115 return { user, orders };
116}
117
118// 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})();
128
129// 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}
136
137async function consumeAsyncIterator() {
138 for await (const num of generateNumbers(5)) {
139 console.log("Got number:", num);
140 }
141}
142
143// 8. Promise.all with error handling per item
144async 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 );
152
153 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}
160
161// Mock helpers
162function 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:

  1. Refactor a callback-based function to use async/await — compare readability
  2. Write a function that fetches 10 URLs in parallel using Promise.all() and async/await
  3. Implement controlled concurrency: process 100 items with max 5 concurrent operations
  4. Create a Go-style to() error wrapper and use it in a 3-step async flow
  5. Build an async generator that reads a file line by line and yields parsed JSON objects
  6. Write a function that races a fetch request against a timeout, with proper cleanup of the losing operation

⚠️ Common Mistakes

  • Using await in series when operations are independent — const a = await x(); const b = await y(); is sequential; use Promise.all([x(), y()]) for parallel

  • Using forEach with awaitarray.forEach(async (item) => { await process(item); }) does NOT wait for each item; use for...of for sequential or Promise.all(array.map(...)) for parallel

  • Forgetting try/catch around await — unhandled Promise rejections crash the process; always wrap async code in error handlers

  • Making every function async even when it doesn't need to be — adding async to synchronous functions adds unnecessary Promise wrapping overhead

  • Not understanding that await only 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.