Promises
1) What is a Promise?
An object representing a value that may arrive later.
States: pending → fulfilled (value) or pending → rejected (reason).
Handlers:
.then(onFulfilled?, onRejected?).catch(onRejected).finally(onFinally)(no args; runs either way)
Tiny start
new Promise(res => setTimeout(() => res("done"), 300))
.then(v => console.log(v)); // "done"
Promise.reject(new Error("boom"))
.catch(e => console.log("caught:", e.message))
.finally(() => console.log("cleanup"));
2) Async/Await (sugar over Promises)
async fn returns a Promise. await pauses inside it until the Promise settles.
async function getTotal(userId) {
try {
const user = await fetchUser(userId);
const orders = await fetchOrders(user.id);
return orders.reduce((s, o) => s + o.total, 0);
} catch (e) {
throw new Error("Total failed: " + e.message);
}
}
3) Chaining & Error Rules
- Returning a value in
.then→ next link gets that value. - Returning a Promise → chain waits for it.
- Throw in
.then→ becomes a rejection down the chain. .catchhandles any rejection above it; if it returns a value, chain becomes fulfilled again.
doThing()
.then(() => { throw new Error("fail"); })
.catch(() => "recovered")
.then(v => console.log(v)); // "recovered"
4) Combinators (Concurrency Tools)
await Promise.all([a(), b()]); // all succeed or reject fast
await Promise.allSettled([a(), b()]); // always resolves with per-item status
await Promise.race([a(), b()]); // first settle (fulfill OR reject)
await Promise.any([a(), b()]); // first fulfill; rejects if all reject
5) Event Loop & Microtasks (execution order)
- Sync code first.
- Then microtasks (Promises).
- Then macrotasks (
setTimeout, I/O).
console.log("A");
setTimeout(() => console.log("D"), 0);
Promise.resolve().then(() => console.log("B"));
console.log("C");
// A, C, B, D
6) Real‑World Patterns (simple + solid)
A) Retry with Exponential Backoff (gentle)
async function retry(fn, { retries = 3, base = 200 } = {}) {
for (let attempt = 0; ; attempt++) {
try { return await fn(); }
catch (e) {
if (attempt >= retries) throw e;
const wait = base * 2 ** attempt; // 200, 400, 800, ...
await new Promise(r => setTimeout(r, wait));
}
}
}
B) Limited Concurrency (only N running)
async function runWithPool(tasks, poolSize = 2) {
const results = [];
let i = 0;
async function worker() {
while (i < tasks.length) {
const idx = i++;
results[idx] = await tasks[idx]();
}
}
await Promise.all(Array.from({ length: Math.min(poolSize, tasks.length) }, worker));
return results;
}
// usage: tasks = [() => fetch(...), () => fetch(...), ...]
C) Memoize Async (dedupe in‑flight)
function memoizeAsync(fn) {
const cache = new Map(); // key → Promise
return async (key) => {
if (!cache.has(key)) cache.set(key, fn(key).finally(() => {}));
return cache.get(key);
};
}
D) Timeout + AbortController
function fetchWithTimeout(url, { ms = 2000, ...opts } = {}) {
const controller = new AbortController();
const t = setTimeout(() => controller.abort("timeout"), ms);
return fetch(url, { ...opts, signal: controller.signal }).finally(() => clearTimeout(t));
}
E) End‑to‑End Mini Demo (timeout + retry + parallel)
async function getJson(url) {
return retry(async () => {
const res = await fetchWithTimeout(url, { ms: 1500 });
if (!res.ok) throw new Error("HTTP " + res.status);
return res.json();
}, { retries: 2, base: 200 });
}
(async () => {
try {
const [users, posts] = await Promise.all([
getJson("https://jsonplaceholder.typicode.com/users"),
getJson("https://jsonplaceholder.typicode.com/posts"),
]);
console.log("users:", users.length, "posts:", posts.length);
} catch (e) {
console.error("Failed:", e.message);
}
})();
7) Static Helpers (what exists today)
Promise.resolve(value)Promise.reject(reason)Promise.all(iterable)Promise.allSettled(iterable)Promise.race(iterable)Promise.any(iterable)
Note: Proposed (not universally available yet):
Promise.isFulfilled,isRejected,isSettled, etc. Until then, track manually:
function track(p) {
let state = "pending", result;
const t = p.then(
v => (state = "fulfilled", result = v, v),
e => { state = "rejected"; result = e; throw e; }
);
t.getState = () => state; t.getResult = () => result;
return t;
}
8) Unhandled Rejections
- Browser:
window.addEventListener('unhandledrejection', e => ...) - Node:
process.on('unhandledRejection', (reason, p) => ...)
Always ensure every Promise chain has a .catch() (or is awaited in a try/catch).
9) Best Practices & Gotchas
- Prefer
awaitfor readability; return early. - Always
returninside.then()chains to keep chaining correct. - Use one
.catch()per chain unless you can recover earlier. - Use
finallyfor cleanup only (it doesn’t receive result). - Run independent async work in parallel via
Promise.all. - Use backoff + jitter for retries; consider idempotency.
- Don’t block the thread with heavy sync work before resolving.
30 Interview Q&As (sharp & succinct)
-
Promise states? Can it settle twice? A:
pending → fulfilledorpending → rejected. Extra resolve/reject calls after settlement are ignored. -
Return value vs return Promise vs throw in
.then()? A:return 5→ next.then(5);return p→ waits;throw e→ rejection flows to next.catch(). -
Order puzzle:
console,Promise.then,setTimeout(0). A: Sync logs → all microtasks (Promises) → macrotasks (timeouts). -
allvsallSettled? When to use which? A:all= all must succeed (fail fast).allSettled= collect results/errors for best‑effort/reporting. -
racevsanydifference? A:race: first to settle (fulfill or reject).any: first to fulfill, rejects only if all reject (AggregateError). -
Thenable definition + risk? A: Object with
.then.Promise.resolve(thenable)adopts it; untrusted thenables can misbehave or call handlers oddly. -
Promise cancellation? A: Not natively; use
AbortControllerwith APIs that supportsignal(e.g.,fetch). -
Can
.finally()change the result? A: No; it passes through prior outcome unless it throws, which turns chain into a rejection. -
What is an unhandled rejection? Why care? A: A rejection without a catch. Leads to noisy logs, potential process crashes; always handle or log.
-
Where to place
.catch()? A: Usually at the end. Place inner.catch()only to recover from specific errors and continue. -
Awaiting non‑Promise? A:
await 42isawait Promise.resolve(42)→ continues next microtask. -
Mixing
awaitand.then()okay? A: Yes, but prefer consistent style..thencan be fine for one‑liners;awaitis clearer for branching/try‑catch. -
Exponential backoff & jitter—why? A: To reduce server stampedes; jitter randomizes waits and avoids synchronized retries.
-
Implement a simple concurrency limiter idea. A: Spawn N workers sharing an index; each pulls the next task until none left (see pool example).
-
Promise.allshort‑circuit behavior. A: Rejects immediately on first failure; other results ignored (underlying tasks may still run). -
Promise.resolve()vsnew Promise(r => r())? A: Both fulfill;resolve()is simpler/cheaper. Both queue handlers as microtasks. -
Common bug: forgotten
returninside.then()? A: Next.thenruns too early withundefined. Fix: always return the value/Promise. -
allSettledhiding critical failures—what do? A: After results, scan for critical failures and throw aggregated error; otherwise proceed. -
awaitinside a loop—when bad? A: Serializes independent tasks. PreferPromise.all(map(...)). Use serial when order/dependency is required. -
Deduping in‑flight requests? A: Cache Promises by key; if present, return the cached Promise (memoizeAsync).
-
Microtasks vs macrotasks names? A: Microtasks = PromiseJobs/
queueMicrotask; Macrotasks = timers, I/O, message events. -
Top‑level
awaitimpact? A: Module load may pause; affects startup ordering; bundlers/loaders must support async module graphs. -
Bridge EventEmitter to a Promise once. A: Return
new Promise((res, rej) => { emitter.once('event', res); emitter.once('error', rej); }); remember to remove listeners if needed. -
Timeout without leaking timers? A:
setTimeout+ abort/settle + clearTimeout infinally(seefetchWithTimeout). -
Use
racefor timeout orAbortController? A:raceworks everywhere but doesn’t stop work; AbortController cancels underlying operation if supported. -
.catch()placement effect. A: It only handles rejections above it. Errors after it need another.catch(). -
asyncthrow vs sync throw before firstawait? A: Both become a rejected Promise;try/catcharoundawaitwill catch either. -
Is
.finally()good for resource cleanup? A: Yes—close connections, clear timers, release locks—runs on both success/failure. -
Preserve order while running in parallel? A: Map in index order and
await Promise.all, or write results toresults[i]as each finishes (pool example does this). -
Circuit breaker sketch with Promises. A: Track failures/time window; after threshold → open (fail fast). After cool‑down → half‑open (probe). Success → close.
Quick Reference (copy/paste)
// Retry
const retry = async (fn, {retries=3, base=200}={}) => {
for (let i=0;; i++) try { return await fn(); }
catch (e) { if (i>=retries) throw e; await new Promise(r=>setTimeout(r, base*2**i)); }
};
// Pool
const runWithPool = async (tasks, n=2) => {
const res=[], L=tasks.length; let i=0;
const worker = async ()=>{ while(i<L){ const k=i++; res[k]=await tasks[k](); } };
await Promise.all(Array.from({length: Math.min(n,L)}, worker)); return res;
};
// Memoize async
const memoizeAsync = (fn) => {
const cache=new Map();
return async (key)=> cache.get(key) ?? cache.set(key, fn(key).finally(()=>{})).get(key);
};
// Timeout + fetch
const fetchWithTimeout = (url,{ms=2000,...o}={})=>{
const c=new AbortController(), t=setTimeout(()=>c.abort("timeout"), ms);
return fetch(url,{...o, signal:c.signal}).finally(()=>clearTimeout(t));
};