Promises and async/await explained
A promise is a placeholder for a value that arrives later. async/await lets you write code that waits for promises as if it were ordinary step-by-step code.
JavaScript runs your code on a single thread. Slow work such as network requests, timers and file reads is handed off, and you get a promise back immediately. The promise is later fulfilled with a value or rejected with an error.
async and await
An async function always returns a promise. Inside it, await pauses that function (not the whole program) until a promise settles, then gives you its value or throws its error.
async function loadUser(id) {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
}
try {
const user = await loadUser(1);
console.log(user.name);
} catch (err) {
console.error("Could not load user:", err.message);
}
Errors from awaited promises are caught by ordinary try/catch. Top-level await, as in the last lines above, works in ES modules (.mjs files, type="module" scripts) but not in CommonJS.
Creating a promise yourself
You rarely need new Promise, except to wrap a callback-based API. A small wait helper is the usual example:
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
await wait(500);
Node already ships promise versions of most APIs, such as node:fs/promises and setTimeout from node:timers/promises, so check before wrapping.
Sequential versus parallel
The most common performance mistake is awaiting independent tasks one after another. Each await in a loop waits for the previous one to finish:
// Sequential: total time is the sum of all requests
for (const id of ids) {
results.push(await loadUser(id));
}
// Parallel: start everything, then wait once
const results = await Promise.all(ids.map(loadUser));
Use the sequential form only when each step depends on the previous result, or when you must not overload a server. For a bounded middle ground, run tasks with a concurrency limit.
Combining promises
Promise.all(list): waits for all; rejects as soon as any one rejects.Promise.allSettled(list): waits for all and reports each as{ status: "fulfilled", value }or{ status: "rejected", reason }. Good for batch jobs where partial success is fine.Promise.race(list): settles like whichever promise settles first, success or failure.Promise.any(list): the first one to succeed; rejects with anAggregateErroronly if all fail.Promise.withResolvers(): returns{ promise, resolve, reject }for cases where resolving happens outside the constructor callback.
Common pitfalls
- Forgetting await.
const data = loadUser(1)stores a pending promise, not the user. A linter rule such as@typescript-eslint/no-floating-promisescatches this. - Unhandled rejections. A rejected promise that nobody catches makes Node print the error and exit with a non-zero code. Always
awaitor attach.catch(). - async callbacks in forEach.
forEachignores returned promises, so the code after it does not wait. Usefor...oforPromise.allwithmap. - return versus return await inside
try. If you writereturn promisein atryblock, a rejection skips yourcatch. Usereturn await promisethere.
How it fits the event loop
Code after an await runs as a microtask once the awaited promise settles. Microtasks run after the current synchronous code finishes and before the next timer or I/O callback. That is why await never blocks other work, and why a long synchronous loop still freezes everything, async function or not.
See also
- 0019 fetch with timeout, retries and proper error handling A reusable fetchJson helper for Node 20+ and browsers that adds a timeout, retries with backoff for temporary failures, and clear errors… Intermediate · How-to · Any system Script Async code · Making requests 15 Oct 2026
- 0023 Run async tasks with a concurrency limit A small mapLimit helper that runs an async function over many items with at most N in flight, keeps result order, stops… Advanced · How-to · Any system Script Async code 15 Oct 2026
- 0085 fetch with timeout, retries, and exponential backoff A drop-in fetch wrapper that times out slow requests, retries network errors and 429/5xx responses with jittered exponential backoff, and honors Retry-After. Advanced · How-to · Any system, Browser Script Async code · Making requests 15 Oct 2026
- 0087 CORS explained: why the browser blocks your request CORS is the browser rule that decides whether JavaScript on one origin may read a response from another. The server opts in… Intermediate · Concept · Browser Article Async code · Making requests 15 Oct 2026
Repos in the wild
History
Every saved edit is kept. The plot on the sleeve draws one line per edit. Open any edit to see exactly what changed.
- commitpedia-admin +3,449 chars