Async/Await: Sequential vs Parallel & Loop Trap
await likha = function ruka, thread nahi. Loop mein await = SLOW. Promise.all + map = FAST.
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 42 → 42. 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.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 };
}
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.
Promise.all + map. If yes (each step needs previous result), sequential is correct.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 };
}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
Want to track your progress?
Log in to save your place and pick up where you left off.
Progress track karna chahte ho?
Login karo apni progress save karne ke liye aur jahan chhoda tha wahan se shuru karo.
Login