⚡ Async JavaScript
Master Cheatsheet
Event Loop • Promises • Async/Await • Error Handling • Patterns
Created by: Neeraj | LinkedIn: neeraj-kumar1904 💼 | X: @_19_neeraj 🐦 | GitHub: Neeraj05042001 🐙
1. THE EVENT LOOP — Foundation of Everything
Mental Model — The 4 Boxes
JavaScript is single-threaded. Only one thing runs at a time. The Event Loop decides WHAT runs WHEN.
Call Stack Web APIs Microtask Queue Callback Queue
Where your code runs Where setTimeout, Where Promises Where setTimeout
right now fetch, DOM events resolve (HIGH callbacks wait (LOW
live priority) priority)
Priority Rule — Most Important Thing to Remember
Microtask Queue (Promises) ALWAYS runs before Callback Queue (setTimeout)
Predict the Output — Test Your Understanding
[Link]('1'); // sync → runs immediately
setTimeout(() => [Link]('2'), 0); // callback queue → last
[Link]().then(() => [Link]('3'));// microtask queue → before setTimeout
[Link]('4'); // sync → runs immediately
// Output: 1, 4, 3, 2
The Loop Rule
When call stack is empty... Event loop checks microtask queue first,
then callback queue
Promise resolves Goes to microtask queue → runs BEFORE any
setTimeout
setTimeout fires Goes to callback queue → runs AFTER all
promises settle
await keyword Pauses current function, frees the call
stack for other work
2. PROMISES — The Building Block
Promise States — 3 and only 3
State Meaning What happens
Pending Still waiting .then() and .catch() wait
Fulfilled ✅ Resolved with a value .then() runs
Rejected ❌ Failed with an error .catch() runs
KEY: Once a Promise settles (fulfilled or rejected) it can NEVER change state again
Chaining — Every .then() returns a NEW Promise
[Link](1)
.then(x => x + 1) // x=1, returns 2
.then(x => x * 2) // x=2, returns 4
.then(x => [Link](x)); // 4
// Errors skip .then() and jump to .catch()
[Link](new Error('boom'))
.then(x => x + 1) // SKIPPED
.then(x => x * 2) // SKIPPED
.catch(err => [Link]([Link])); // 'boom'
Key Methods Reference
.then(onSuccess) Runs when promise resolves. Returns a new promise.
.catch(onError) Runs when promise rejects. Same as .then(null, onError).
.finally(fn) Runs on BOTH resolve and reject. Good for cleanup — but
has timing gotchas.
.then(onSuccess, onError) Handle both in one call. Safer timing than chaining
.catch() separately.
Sharing a Promise — The Key Insight
// A Promise is just an OBJECT. Multiple callers can .then() the same one.
const sharedPromise = fetch('/api/data'); // ONE network call
[Link](r => [Link]('Caller A:', r));
[Link](r => [Link]('Caller B:', r));
[Link](r => [Link]('Caller C:', r));
// All 3 get the same result. Only 1 request was made.
3. ASYNC / AWAIT — Promise Sugar
The Golden Rule
async/await is just Promise syntax. Every async function returns a Promise. await unwraps
it.
Side-by-Side Comparison
// Promise version // async/await version
function getData() { async function getData() {
return fetch('/api') const res = await fetch('/api');
.then(res => [Link]()) const data = await [Link]();
.then(data => [Link]); return [Link];
} }
// Both are IDENTICAL under the hood
Critical Traps
❌ WRONG (Sequential — slow) ✅ RIGHT (Parallel — fast)
for (const id of ids) { await fetch(id); await [Link]( [Link](id =>
// waits each time } fetch(id)) );
// await pauses only the CURRENT function, not the whole program
// Other async operations continue running while you await
// async function ALWAYS returns a Promise even if you return a plain value
async function greet() { return 'hello'; }
greet(); // → Promise<'hello'>, not just 'hello'
4. ERROR HANDLING — The Part Everyone Gets Wrong
How Errors Travel
// Error propagates down the chain, skipping .then(), until caught
asyncOperation() // throws/rejects
.then(doStepA) // SKIPPED
.then(doStepB) // SKIPPED
.catch(handleError) // ← error arrives here
.then(doStepC); // runs again after catch (catch returns resolved promise)
Cleanup — .finally() vs .then() + .catch()
.finally() Runs after resolve OR reject. Simple cleanup. But has
microtask TIMING ISSUES in some cases (avoid for cache
cleanup).
.then(fn, fn) Handles both in same microtask tick. Use when cleanup timing
matters (like deleting from a Map/cache).
try { } catch { } Async/await equivalent of the above. finally block always
finally { } runs.
Why Delete in BOTH .then() and .catch()
// If you only cleanup on SUCCESS:
// → failed request stays in cache forever → callers can never retry
// If you only cleanup on FAILURE:
// → successful request stays in cache → callers get stale data forever
// ✅ Always cleanup in BOTH:
[Link](
(result) => { [Link](key); return result; }, // success: cleanup + pass value
(err) => { [Link](key); throw err; } // failure: cleanup + re-throw
);
Unhandled Rejection — [Link] Crashes
Any rejected promise with no .catch() will CRASH [Link]. Always handle errors.
// Add a .catch(() => {}) on side-chains to prevent crash
// while still letting the RETURNED promise carry the real error to the caller
const promise = apiCall(id);
[Link](() => {}); // suppress crash on side-chain
return promise; // caller still gets the real rejection
5. CONCURRENCY PATTERNS — The Toolkit
Promise Combinators — Which one to use?
Method Behavior Use when...
[Link]([]) All must succeed. Fails fast You need ALL results and can't
if ANY rejects. proceed if any fail
[Link]([]) Runs all. Never fails. You want all results regardless
Returns status for each. of failures
[Link]([]) First to settle wins (resolve Timeout logic — race fetch vs
OR reject). sleep(5000)
[Link]([]) First to SUCCEED wins. Fallbacks — try multiple
Ignores rejections. sources, use first success
Pattern: Deduplication — Share In-Flight Promises
Trigger: Multiple callers want the same async result at the same time
const cache = new Map();
function deduped(id, apiCall) {
if ([Link](id)) return [Link](id); // return SAME promise
const promise = apiCall(id)
.then(result => { [Link](id); return result; }) // cleanup both
.catch(err => { [Link](id); throw err; }); // cases!
[Link](id, promise);
return promise;
}
Pattern: Async Recursion — Follow a Chain
Trigger: Response tells you to fetch again with a different ID (redirects, pagination,
trees)
async function fetchWithRedirect(id, fetcher, depth = 0, maxDepth = 5) {
if (depth > maxDepth) throw new Error('Max redirect depth exceeded');
const response = await fetcher(id);
if ([Link]) {
return fetchWithRedirect([Link], fetcher, depth + 1, maxDepth);
}
return response; // base case — no redirect, we're done
}
Pattern: Parallel + Redirect (combining patterns)
Trigger: Array/object of IDs, each may redirect, all should run at the same time
async function fetchDeep(ids, fetcher, maxDepth = 5) {
async function fetchOne(id, depth) {
if (depth > maxDepth) throw new Error('Max redirect depth exceeded');
const res = await fetcher(id);
if ([Link]) return fetchOne([Link], depth + 1);
return res;
}
const keys = [Link](ids);
const results = await [Link]([Link](k => fetchOne(ids[k], 0)));
return [Link]([Link]((k, i) => [k, results[i]]));
}
Pattern: Retry with Backoff
Trigger: Network call may fail temporarily, try again automatically
async function withRetry(fn, retries = 3, delayMs = 100) {
try {
return await fn();
} catch (err) {
if (retries === 0) throw err;
await new Promise(r => setTimeout(r, delayMs));
return withRetry(fn, retries - 1, delayMs * 2); // exponential backoff
}
}
Pattern: Timeout
Trigger: You want to fail fast if a request takes too long
function withTimeout(promise, ms) {
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timed out')), ms)
);
return [Link]([promise, timeout]); // first to settle wins
}
6. PATTERN RECOGNITION — See the Problem, Know the Tool
The 6 Questions to Ask on Every Problem
Question Answer Reach for
Multiple callers want the same thing at Yes Deduplication (Map + shared
the same time? Promise)
Does each step depend on the result of Yes Async Recursion or
the previous? sequential await
Can things run at the same time Yes [Link]([Link](...))
independently?
Can it run forever / loop back on Yes Depth counter OR visited Set
itself?
Does it need to fail fast if too slow? Yes [Link]([fetch,
timeout])
Can it fail temporarily but succeed on Yes Retry with exponential
retry? backoff
Data Structure Quick Pick
Map Cache: key → in-flight Promise. Deduplication, memoization. Use
.has(), .get(), .set(), .delete()
Set Track visited IDs. Loop detection. O(1) lookup with .has()
Array Collect promises for [Link](). [Link](fn) → array of
promises
Object Build structured output: { key: result } — use [Link]() +
[Link]()
7. COMMON MISTAKES — What Will Bite You
Mistake Symptom Fix
await inside a regular for Everything runs Use [Link] + .map()
loop sequentially (slow)
Not passing id to apiCall() Returns 'data-undefined' apiCall(id) not apiCall()
Cache inside the function Cache resets every call — Cache must live OUTSIDE the
dedup never works function
Storing result, not Promise Duplicates fire before Store the Promise object
result ready itself
.finally() for cache Timing issues — cache not Use .then(fn, fn) for explicit
cleanup cleared in time cleanup
Unhandled promise rejection [Link] crashes with Add .catch() on all promise
'Error: boom' chains
No base case in recursion Stack overflow / infinite Always check: if (!redirectId)
loop return
Forgetting to re-throw in Error swallowed, caller Always: catch(err => {
.catch() thinks it succeeded cleanup; throw err; })
8. MENTAL MODELS — Think in Pictures
The Restaurant Analogy
// JavaScript = one waiter (single thread)
// Kitchen = Web APIs (setTimeout, fetch — work happens here in background)
// Order tickets = Microtask queue (Promise callbacks — handled first)
// Walk-in requests = Callback queue (setTimeout — handled after tickets)
// Waiter takes order → gives to kitchen → serves OTHER tables while waiting
// Kitchen calls back → waiter finishes current table → picks up that order
Promise Chain = Assembly Line
fetch('/api') // Station 1: get raw response
.then(r => [Link]()) // Station 2: parse to JSON
.then(d => [Link]) // Station 3: extract users array
.catch(handleError) // Quality control: catch any defects from ANY station
.finally(cleanup); // Cleanup: always runs at the end
Recursion = Russian Dolls
// Each doll (call) opens the next one until you hit the smallest (base case)
fetchWithRedirect('id-1') // opens → redirects to id-2
fetchWithRedirect('id-2') // opens → redirects to id-3
fetchWithRedirect('id-3') // opens → NO redirect → return data
// data flows back out through each doll
[Link] = Parallel Kitchen Stations
// All stations start cooking AT THE SAME TIME
// You wait until the LAST one is ready
// If ANY station fails → whole order fails
await [Link]([
fetch('/api/users'), // starts immediately
fetch('/api/products'), // starts immediately
fetch('/api/orders'), // starts immediately
]); // wait for all 3 to finish
Async JavaScript Master Cheatsheet • Built from real problems, real bugs, real fixes