Chapter 9.3☕ 45 min read

Async/Await: Sequential vs Parallel & Loop Trap

await likha = function ruka, thread nahi. Loop mein await = SLOW. Promise.all + map = FAST.

01🔐 The Loop Trap — IIFE ka Bhaangar

async function always returns a Promise. If you return a value, it's wrapped in Promise.resolve(value). If you throw, it's wrapped in Promise.reject(error).

await pauses the async function (not the thread!). Other code continues running. The event loop keeps processing.

await promise — if fulfills: returns value. If rejects: throws error (catchable with try/catch).

await with non-promises: await 4242. Non-promises are wrapped in Promise.resolve() first.

async arrow functions: const fn = async () => {}. async methods in classes: async method() {}.

Top-level await: Only in ES modules. In scripts, use async IIFE: (async () => { await ... })().

// async function always returns a Promise
async function getUser() {
  return "Sai"; // Wrapped in Promise.resolve("Sai")
}
getUser().then(name => console.log(name)); // "Sai"

async function failing() {
  throw new Error("Oops!"); // Wrapped in Promise.reject
}
failing().catch(err => console.error(err.message)); // "Oops!"

// await pauses the function, NOT the thread
async function demo() {
  console.log("1 — inside async");
  const result = await new Promise(resolve => {
    console.log("2 — inside Promise executor (sync!)");
    setTimeout(() => resolve("done"), 1000);
  });
  console.log("3 — after await:", result);
}
console.log("0 — before calling demo");
demo();
console.log("4 — after calling demo");
// Output: 0, 1, 2, 4, (1 second wait), 3
// await yields control back to the caller.
// After Promise resolves, execution resumes after await.
02📦 Module Pattern — Closures ka Superpower

Sequential: const a = await fetchA(); const b = await fetchB(); — fetchB waits for fetchA. Total = A + B.

Parallel: const [a, b] = await Promise.all([fetchA(), fetchB()]); — both start simultaneously. Total = max(A, B).

Use sequential when: second request depends on first's result (fetch user → fetch their orders).

Use parallel when: requests are independent (fetch user, fetch notifications, fetch weather).

Getting parallel wrong is the #1 async/await performance mistake.

// ❌ SEQUENTIAL — 3 seconds total (1s + 1s + 1s)
async function loadPageSlow() {
  const user = await fetch("/api/user");       // 1 second
  const orders = await fetch("/api/orders");   // 1 second (waits for user!)
  const weather = await fetch("/api/weather"); // 1 second (waits for orders!)
  return { user, orders, weather };
}
// Total: 3 seconds! Each request waits for the previous one.

// ✅ PARALLEL — 1 second total (all start at once)
async function loadPageFast() {
  const [user, orders, weather] = await Promise.all([
    fetch("/api/user"),       // Starts immediately!
    fetch("/api/orders"),     // Starts immediately!
    fetch("/api/weather")     // Starts immediately!
  ]);
  return { user, orders, weather };
}
// Total: 1 second! All requests start simultaneously.
// Promise.all waits for all. Time = slowest request.

// ✅ MIXED — dependent sequential, independent parallel
async function loadDashboard() {
  const user = await fetch("/api/user"); // Need user first
  const [orders, notifications] = await Promise.all([
    fetch("/api/orders?userId=" + user.id),       // Depends on user
    fetch("/api/notifications?userId=" + user.id) // Depends on user
  ]);
  return { user, orders, notifications };
}
await under the hood: await compiles to a yield-and-resume in V8 — similar to generators but with promise-specific handling. Each await creates a microtask (the continuation). TurboFan can optimize away await on already-resolved promises — if V8 can prove the promise is resolved, it skips the suspend/resume overhead.
03🧩 Revealing Module & Namespacing

THE TRAP: for (const url of urls) { await fetch(url); } — each fetch waits for the previous one!

10 URLs × 1s each = 10 seconds SEQUENTIAL. You expected 1 second parallel.

Fix 1: Promise.all(urls.map(url => fetch(url))) — all start simultaneously. Total = slowest.

Fix 2: Batch with concurrency limit — process N at a time to avoid overwhelming the server.

When you DO want sequential: processing results one at a time where order matters, or each step depends on previous.

Map + Promise.all is the standard pattern for parallel async operations on arrays.

// ❌ THE LOOP TRAP — sequential execution!
async function fetchAllUrls(urls) {
  const results = [];
  for (const url of urls) {
    const res = await fetch(url); // Each waits for the previous!
    results.push(await res.json());
  }
  return results;
}
// 10 URLs × 1s each = 10 seconds!

// ✅ FIX 1: Promise.all + map — fully parallel
async function fetchAllUrlsFast(urls) {
  const results = await Promise.all(
    urls.map(url => fetch(url).then(res => res.json()))
  );
  return results;
}
// 10 URLs × 1s each = 1 second! (max of all)

// ✅ FIX 2: Concurrency limit — process N at a time
async function fetchWithConcurrency(urls, limit = 3) {
  const results = [];
  const executing = new Set();
  for (const url of urls) {
    const promise = fetch(url).then(res => res.json());
    results.push(promise);
    executing.add(promise);
    promise.finally(() => executing.delete(promise));
    if (executing.size >= limit) {
      await Promise.race(executing); // Wait for one to finish
    }
  }
  return Promise.all(results);
}
// Process 3 at a time. Balanced speed and server load.
📋 The await-in-loop trap is the most common async/await mistake in production code. Before writing await inside a loop, ask: "Do these iterations depend on each other?" If no, use Promise.all + map. If yes (each step needs previous result), sequential is correct.
04⚡ Performance: Memory & Leaks

try/catch with await: try { const data = await fetchData(); } catch(err) { /* handles rejection */ }

try/catch catches: sync errors in try block AND await rejections.

try/catch does NOT catch: setTimeout errors (different macrotask), unhandled promise rejections outside try.

Strategy 1: try/catch per await — granular but verbose.

Strategy 2: Single try/catch for multiple awaits — catches any error but can't tell which one failed.

Strategy 3: Wrapper function — const safe = p => p.catch(() => null);

Strategy 4: .catch() on promise before await — inline fallback.

// Strategy 1: try/catch per await (granular)
async function handleEach() {
  let user, orders;
  try {
    user = await fetchUser();
  } catch (err) {
    user = { name: "Guest" }; // Fallback for user
  }
  try {
    orders = await fetchOrders(user.id);
  } catch (err) {
    orders = []; // Fallback for orders
  }
}

// Strategy 2: Single try/catch (simple)
async function handleAll() {
  try {
    const user = await fetchUser();
    const orders = await fetchOrders(user.id);
    return { user, orders };
  } catch (err) {
    console.error("Something failed:", err);
    return { user: null, orders: [] }; // Can't tell which failed
  }
}

// Strategy 3: Wrapper function (parallel + graceful)
const safe = promise => promise.catch(() => null);
async function handleSafe() {
  const [user, orders] = await Promise.all([
    safe(fetchUser()),       // null if fails
    safe(fetchOrders(1))     // null if fails
  ]);
  if (!user) return showLogin();
  if (!orders) return showEmpty();
}

// Strategy 4: .catch before await (inline fallback)
async function handleInline() {
  const user = await fetchUser().catch(() => ({ name: "Guest" }));
  const orders = await fetchOrders(user.id).catch(() => []);
  return { user, orders };
}
05🧹 Practical Patterns — Curry, Memoize, Once

Top-level await: Only in ES modules (type="module"). The module waits for the promise before exporting.

In scripts: Use async IIFE — (async () => { const data = await fetch(url); })().

Common mistake 1: Forgetting await — get Promise object instead of value.

Common mistake 2: Using await in non-async function — SyntaxError.

Common mistake 3: Unnecessary async — function doesn't use await but is marked async (creates unnecessary Promise).

Common mistake 4: return await — redundant inside an async function (just return value). Exception: return await IS needed inside try/catch to catch the rejection before returning.

// Top-level await (ES modules only — .mjs or type="module")
// const config = await fetch("/config.json").then(r => r.json());
// This module won't export until the fetch completes.
// Importing modules wait for this module's top-level await.

// Async IIFE for scripts (non-module)
(async () => {
  const data = await fetch("/api/data");
  console.log(data);
})(); // Immediately invoked!

// ❌ Mistake 1: Forgetting await
async function getData() {
  const response = fetch("/api/data"); // Missing await!
  console.log(response); // Promise object, not data!
}
// ✅ Fix:
async function getData() {
  const response = await fetch("/api/data"); // Wait for it!
  console.log(response); // Response object
}

// ❌ Mistake 2: await in non-async function
function bad() {
  // await fetch("/api"); // SyntaxError!
}
// ✅ Fix: Add async keyword
async function good() {
  await fetch("/api"); // Works!
}

// ❌ Mistake 4: Unnecessary return await
async function getValue() {
  return await Promise.resolve(42); // Extra microtask!
}
// ✅ Just return the value
async function getValue() {
  return 42; // Same result, fewer microtasks
}
// Exception: return await IS needed inside try/catch
async function safeGetValue() {
  try {
    return await riskyPromise(); // ✅ Needed!
  } catch (err) {
    return fallbackValue;
  }
}

Lo kar liya — Key Points:

  • ✅ async functions always return Promises; await pauses the function (not the thread) until the Promise settles
  • ✅ Sequential await (one after another) = total time = sum of all operations; parallel (Promise.all) = max of all
  • ✅ The await-in-loop trap causes serial execution — use Promise.all + map for parallel processing
  • ✅ Use concurrency limits (Promise.all + race pattern) when you need parallel but bounded execution
  • ✅ try/catch with await catches sync errors and Promise rejections; setTimeout errors are NOT caught
  • ✅ Wrapper functions (const safe = p => p.catch(() => null)) allow graceful degradation in parallel operations
  • ✅ Top-level await only works in ES modules; use async IIFE for scripts
  • ✅ Forgetting await returns a Promise object instead of the resolved value — the most common async/await bug
Course Search
Search across all chapters & stages
📖

Search the course

Type any topic — branching, stash, rebase, hooks — and jump straight to that chapter.

merge branchesgit stashundo commitrebase