Concurrency#
Concurrency in TypeScript is JavaScript’s (see
Concurrency): a single event
loop, Promise<T>, async / await, AbortController,
and worker threads. The type layer carries promise types
through (Promise<User>), strips them with Awaited<T>,
and types worker message structures.
This page covers the TypeScript-specific patterns. For the runtime mechanics, see the JavaScript page.
Typed promises#
Promise<T> carries the resolved type; async functions
return Promise<T> where T is the function’s return type.
async function getUser(id: string): Promise<User> {
const r = await fetch(`/users/${id}`);
if (!r.ok) throw new Error(`HTTP ${r.status}`);
return r.json() as Promise<User>;
}
const user: User = await getUser("1");
For unwrapping nested promises, Awaited<T> (the
deep-unwrap helper) drops every layer.
type Inner = Awaited<Promise<Promise<User>>>; // User
Promise combinators (typed)#
The combinators carry types automatically when the array elements have known types.
// Promise.all: tuple in, tuple out
const [a, b]: [User, Org] = await Promise.all([loadUser(id), loadOrg(id)]);
// Promise.allSettled: per-element result shape
const results = await Promise.allSettled(urls.map(fetch));
const ok = results.flatMap(r => r.status === "fulfilled" ? [r.value] : []);
// ^? Response[]
// Promise.race: first to settle
const winner = await Promise.race([primary(), backup()]);
// ^? T (the common type)
// Promise.any: AggregateError if all reject
try { const v = await Promise.any(candidates); }
catch (e) { if (e instanceof AggregateError) /* … */; }
Cancellation with AbortSignal#
AbortController is typed; signal is AbortSignal.
Most async APIs accept it.
const ctrl = new AbortController();
const t = setTimeout(() => ctrl.abort(), 5000);
try {
const r: Response = await fetch(url, {signal: ctrl.signal});
} catch (e) {
if (e instanceof Error && e.name === "AbortError") log.warn("timed out");
else throw e;
} finally {
clearTimeout(t);
}
// shortcut
const r2 = await fetch(url, {signal: AbortSignal.timeout(3000)});
Concurrency cap#
A typed pool function keeps N requests in flight at a time.
async function pool<T, U>(items: T[], n: number, fn: (item: T) => Promise<U>): Promise<U[]> {
const out: U[] = [];
const queue = [...items];
await Promise.all(Array.from({length: n}, async () => {
while (queue.length) {
const item = queue.shift()!;
out.push(await fn(item));
}
}));
return out;
}
const responses: Response[] = await pool(urls, 5, u => fetch(u));
Worker threads with typed messages#
Node’s worker_threads is untyped at the message boundary; the
operator wraps it with a shared message-type definition.
// protocol.ts
export type WorkerMessage =
| {kind: "hash"; data: Uint8Array}
| {kind: "exit"};
export type WorkerResponse =
| {kind: "hash"; sha256: string}
| {kind: "error"; message: string};
// main.ts
import {Worker} from "node:worker_threads";
import type {WorkerMessage, WorkerResponse} from "./protocol.js";
const w = new Worker("./worker.js");
w.on("message", (m: WorkerResponse) => {
if (m.kind === "hash") console.log(m.sha256);
});
const req: WorkerMessage = {kind: "hash", data};
w.postMessage(req);
// worker.ts
import {parentPort} from "node:worker_threads";
import {createHash} from "node:crypto";
import type {WorkerMessage, WorkerResponse} from "./protocol.js";
parentPort!.on("message", (m: WorkerMessage) => {
if (m.kind === "hash") {
const sha256 = createHash("sha256").update(m.data).digest("hex");
const reply: WorkerResponse = {kind: "hash", sha256};
parentPort!.postMessage(reply);
}
});
The discriminant (kind) drives both the parsing inside the
worker and the type narrowing on the main side.
Async generators#
async function* yields typed values; for await...of
consumes them.
async function* paginate(url: string): AsyncGenerator<User, void, undefined> {
let next: string | null = url;
while (next) {
const r: Response = await fetch(next);
const page: {users: User[]; next: string | null} = await r.json();
for (const u of page.users) yield u;
next = page.next;
}
}
for await (const u of paginate("/users")) {
handle(u); // u: User
}
Effect (advanced)#
For programs where you want typed concurrency,
cancellation, retries, and dependency injection together, the
effect library models computation
as Effect<R, E, A> (requirements, error, success). It
replaces Promise / try / catch with a typed pipeline.
This is heavyweight; reach for it on long-lived services with complex error flow, not for one-off scripts.
References#
Concurrency for the event loop, microtasks, and promise mechanics.
Functions for
asyncsignatures andAwaited.Errors for promise rejection and typed catches.