Async cheatsheet.
Promise basics
const p = new Promise<number>((resolve, reject) => {
setTimeout(() => resolve(42), 100);
});
p.then((v) => v + 1).catch((e) => console.error(e));
const v = await p; // 42
async / await
async function f(): Promise<number> {
const x = await fetch1();
const y = await fetch2(x);
return y;
}
async functions always return Promise. await only valid inside async (or top-level in ESM modules).
Promise.all
const [users, posts, tags] = await Promise.all([
fetchUsers(),
fetchPosts(),
fetchTags(),
]);
If ANY rejects, the whole thing rejects.
Promise.allSettled
const results = await Promise.allSettled([
risky1(),
risky2(),
]);
for (const r of results) {
if (r.status === "fulfilled") console.log(r.value);
else console.error(r.reason);
}
Never rejects.
Promise.race / Promise.any
// First to settle (resolve OR reject):
const winner = await Promise.race([p1, p2, p3]);
// First to fulfill (skips rejections):
const success = await Promise.any([p1, p2, p3]);
Sequential vs concurrent
// Sequential (slow)
for (const url of urls) {
results.push(await fetch(url));
}
// Concurrent
const results = await Promise.all(urls.map(fetch));
Bounded concurrency (manual)
async function pool<T, R>(
items: T[],
limit: number,
fn: (item: T) => Promise<R>,
): Promise<R[]> {
const results: R[] = [];
const executing: Promise<void>[] = [];
for (const [i, item] of items.entries()) {
const p = fn(item).then((r) => { results[i] = r; });
executing.push(p);
if (executing.length >= limit) {
await Promise.race(executing);
executing.splice(executing.findIndex(p => p), 1);
}
}
await Promise.all(executing);
return results;
}
await pool(urls, 5, fetch);
Or use p-limit:
import pLimit from "p-limit";
const limit = pLimit(5);
const results = await Promise.all(
urls.map((u) => limit(() => fetch(u))),
);
Timeout
function timeout<T>(p: Promise<T>, ms: number): Promise<T> {
return Promise.race([
p,
new Promise<never>((_, rej) => setTimeout(() => rej(new Error("timeout")), ms)),
]);
}
await timeout(fetch("/x"), 5000);
AbortSignal.timeout (Node 18+):
await fetch("/x", { signal: AbortSignal.timeout(5000) });
AbortController
const ctrl = new AbortController();
const p = fetch("/x", { signal: ctrl.signal });
setTimeout(() => ctrl.abort(), 1000);
try {
await p;
} catch (e) {
if (e instanceof DOMException && e.name === "AbortError") {
// handled
}
}
AbortSignal.any (combine):
const sig = AbortSignal.any([ctrl.signal, AbortSignal.timeout(5000)]);
await fetch("/x", { signal: sig });
Retry with backoff
async function retry<T>(
fn: () => Promise<T>,
attempts = 3,
base = 100,
): Promise<T> {
let lastErr: unknown;
for (let i = 0; i < attempts; i++) {
try { return await fn(); }
catch (e) {
lastErr = e;
await new Promise(r => setTimeout(r, base * 2 ** i));
}
}
throw lastErr;
}
await retry(() => fetch("/x").then(r => r.json()));
Deferred
function deferred<T>() {
let resolve!: (v: T) => void;
let reject!: (e: unknown) => void;
const promise = new Promise<T>((res, rej) => {
resolve = res; reject = rej;
});
return { promise, resolve, reject };
}
const d = deferred<number>();
setTimeout(() => d.resolve(42), 100);
const v = await d.promise;
Promise → callback bridge
import { promisify } from "node:util";
import fs from "node:fs";
const readFile = promisify(fs.readFile);
const buf = await readFile("a.txt");
Top-level await
// In an ESM module
const config = await loadConfig();
export { config };
Requires "type": "module" and target: ES2022+.
Async iterators
async function* range(n: number) {
for (let i = 0; i < n; i++) {
yield i;
await new Promise(r => setTimeout(r, 100));
}
}
for await (const x of range(5)) {
console.log(x);
}
Async queue
class Queue<T> {
private items: T[] = [];
private waiters: Array<(v: T) => void> = [];
push(item: T) {
const w = this.waiters.shift();
if (w) w(item);
else this.items.push(item);
}
pop(): Promise<T> {
const item = this.items.shift();
if (item !== undefined) return Promise.resolve(item);
return new Promise(res => this.waiters.push(res));
}
}
Unhandled rejection
process.on("unhandledRejection", (reason) => {
console.error("unhandled:", reason);
});
Always .catch() or await your promises.
Common mistakes
awaitin aforEach— doesn’t wait. Usefor...of.Promise.all([...])with side effects — if one fails, others still run.- Forgetting AbortSignal — leaks long-running requests.
- Returning a promise from a non-async function but not handling rejection.
try/catchonly aroundawait— rejection in fire-and-forget escapes.
Read this next
If you want my async utilities (pool, retry, queue), they’re at rajpoot.dev .
Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .