Node.js Cheatsheet
Async Patterns
Use this Node.js reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
Callbacks (Classic Node Style)
// Error-first callback convention (errback) fs.readFile("file.txt", "utf8", (err, data) => { if (err) return callback(err); // always propagate callback(null, data); // null for "no error" }); // Writing an errback function function readJson(path, cb) { fs.readFile(path, "utf8", (err, text) => { if (err) return cb(err); try { cb(null, JSON.parse(text)); } catch (parseErr) { cb(parseErr); } }); }
Promisify Callbacks
import { promisify } from "node:util"; import fs from "node:fs"; // Convert a single errback function to a promise-returning one const readFile = promisify(fs.readFile); const data = await readFile("file.txt", "utf8"); // Promisify manually function delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } // util.promisify respects the [util.promisify.custom] symbol myLib[util.promisify.custom] = (arg) => new Promise((res, rej) => { ... });
Promises
// Create const p = new Promise((resolve, reject) => { setTimeout(() => resolve("done"), 1000); }); // Consume p.then((val) => console.log(val)) .catch((err) => console.error(err)) .finally(() => console.log("always")); // Promise static methods Promise.resolve(value); // already-resolved Promise.reject(new Error("x")); // already-rejected // Concurrency combinators const [a, b] = await Promise.all([fetchA(), fetchB()]); // all or nothing const results = await Promise.allSettled([p1, p2, p3]); // [{ status: "fulfilled", value }, { status: "rejected", reason }] const settled = await Promise.race([p1, p2]); // first to settle const fulfilled = await Promise.any([p1, p2, p3]); // first to FULFILL // throws AggregateError if all reject
Async / Await
async function fetchUser(id) { const res = await fetch(`/users/${id}`); if (!res.ok) throw new Error(`HTTP ${res.status}`); return res.json(); } // Error handling async function main() { try { const user = await fetchUser(1); console.log(user); } catch (err) { console.error(err); } } // Parallel execution const [users, posts] = await Promise.all([ fetchUsers(), fetchPosts(), ]); // Sequential when order matters for (const id of ids) { await processOne(id); // one at a time } // Parallel with concurrency limit import { default as pLimit } from "p-limit"; const limit = pLimit(5); await Promise.all(ids.map(id => limit(() => processOne(id)))); // Top-level await (ESM only) const config = await fs.promises.readFile("config.json", "utf8"); export default JSON.parse(config);
Async Iterators and Generators
// Async generator — source of async values async function* paginate(url) { let cursor = null; do { const res = await fetch(url + (cursor ? `?cursor=${cursor}` : "")); const page = await res.json(); yield* page.items; cursor = page.nextCursor; } while (cursor); } // Consume with for-await-of for await (const item of paginate("https://api.example.com/items")) { console.log(item); } // Async iterable from array async function* fromArray(arr) { for (const item of arr) yield item; } // Collect async iterable async function collect(iter) { const items = []; for await (const item of iter) items.push(item); return items; } // Pipe two async generators async function* transform(source) { for await (const item of source) { yield item.toUpperCase(); } }
AbortController and AbortSignal
const ac = new AbortController(); const { signal } = ac; // Cancel after timeout const tid = setTimeout(() => ac.abort(new Error("timed out")), 5000); try { const res = await fetch(url, { signal }); clearTimeout(tid); } catch (err) { if (err.name === "AbortError") console.log("cancelled"); else throw err; } // AbortSignal.timeout (Node 17.3+) — no manual cleanup needed const res = await fetch(url, { signal: AbortSignal.timeout(5000) }); // AbortSignal.any (Node 20.3+) — abort when ANY signal fires const signal = AbortSignal.any([signal1, signal2]); // Listen for abort in your own async operation signal.addEventListener("abort", () => cleanup()); if (signal.aborted) throw signal.reason; // early check
Promise Utilities (timers/promises)
import { setTimeout as sleep, setInterval as tick, setImmediate as immediate } from "node:timers/promises"; await sleep(1000); // delay 1 s await sleep(500, "value"); // resolves with "value" await sleep(500, undefined, { signal }); // cancellable for await (const _ of tick(200)) { doWork(); } await immediate(); // next event loop tick
Error Handling Patterns
// Result type pattern (no-throw) async function safeRun(fn) { try { return { ok: true, value: await fn() }; } catch (err) { return { ok: false, error: err }; } } const { ok, value, error } = await safeRun(() => fetchUser(1)); // Retry with exponential backoff async function retry(fn, { attempts = 3, base = 100 } = {}) { for (let i = 0; i < attempts; i++) { try { return await fn(); } catch (err) { if (i === attempts - 1) throw err; await sleep(base * 2 ** i); } } } // Timeout any promise function withTimeout(promise, ms) { return Promise.race([ promise, new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), ms) ), ]); }
Event Loop — Execution Order
// Phase order (simplified): // 1. nextTick queue // 2. microtask queue (Promises, queueMicrotask) // 3. Timers (setTimeout, setInterval callbacks whose time has passed) // 4. Pending callbacks (I/O errors deferred to next loop) // 5. Idle / Prepare // 6. Poll (retrieve I/O events; block here if nothing pending) // 7. Check (setImmediate) // 8. Close callbacks (socket close, etc.) setTimeout(() => console.log("1 timer"), 0); setImmediate(() => console.log("2 immediate")); process.nextTick(() => console.log("3 nextTick")); Promise.resolve().then(() => console.log("4 promise")); queueMicrotask(() => console.log("5 microtask")); // Output: 3 nextTick → 4 promise → 5 microtask → 1 timer → 2 immediate // (timer vs immediate order can vary when NOT inside an I/O callback)
Concurrency Patterns
// Fan-out: run N tasks at once const results = await Promise.all(items.map(processItem)); // Fan-out with max concurrency (manual semaphore) async function mapWithConcurrency(items, fn, limit) { const results = []; const executing = new Set(); for (const item of items) { const p = Promise.resolve().then(() => fn(item)); results.push(p); executing.add(p); p.finally(() => executing.delete(p)); if (executing.size >= limit) await Promise.race(executing); } return Promise.all(results); } // Producer-consumer with async queue async function* producer() { for (let i = 0; i < 100; i++) { await sleep(10); yield i; } } async function consumer(iter, workerId) { for await (const item of iter) { await processItem(item, workerId); } } // Barrier: wait for N things to happen function createBarrier(n) { let resolve; let count = 0; const promise = new Promise(r => resolve = r); return { signal() { if (++count >= n) resolve(); }, wait() { return promise; }, }; }
Async Context Tracking (AsyncLocalStorage)
import { AsyncLocalStorage } from "node:async_hooks"; const requestContext = new AsyncLocalStorage(); // Set context at request entry app.use((req, res, next) => { requestContext.run({ requestId: uuid() }, next); }); // Read anywhere in the async call tree function logSomething(msg) { const ctx = requestContext.getStore(); console.log(`[${ctx?.requestId}] ${msg}`); }