0 day streak
typescript / expert
Snippet

Discriminated Promise Results with `Promise.allSettled` Narrowing

`Promise.all` is fail-fast: one rejection discards every fulfilled value, which is the wrong shape whenever partial success has business meaning (fan-out fetches, batch writes, parallel migrations). `Promise.allSettled` returns a discriminated union per item, and the `'fulfilled' | 'rejected'` tag is what TypeScript narrows on. Note `reason: unknown` — the contract is sound: a rejected promise can reject with anything, including non-Errors. Resist `(reason as Error).message`; type-guard with `reason instanceof Error` instead. The cast `as Settled<T>[]` is the one acceptable spot — the lib type uses `any` for `reason`, and tightening it to `unknown` propagates the right discipline through the rest of the codebase.

snippet.ts
typescript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
type Settled<T> =
| { status: 'fulfilled'; value: T }
| { status: 'rejected'; reason: unknown };
async function partition<T>(
ps: ReadonlyArray<Promise<T>>
): Promise<{ ok: T[]; failed: unknown[] }> {
const results = await Promise.allSettled(ps);
const ok: T[] = [];
const failed: unknown[] = [];
for (const r of results as Settled<T>[]) {
if (r.status === 'fulfilled') {
ok.push(r.value); // r is { status: 'fulfilled'; value: T }
} else {
failed.push(r.reason); // r is { status: 'rejected'; reason: unknown }
}
}
return { ok, failed };
}
const { ok, failed } = await partition<number>([
Promise.resolve(1),
Promise.reject(new Error('boom')),
Promise.resolve(2),
]);
// ok : number[] = [1, 2]
// failed: unknown[] = [Error('boom')]
Breakdown
1
type Settled<T> = { status: 'fulfilled'; value: T } | { status: 'rejected'; reason: unknown };
Hand-tightened discriminated union — replaces the lib's `any` for `reason` with `unknown`, forcing safe error inspection downstream.
2
const results = await Promise.allSettled(ps);
Awaits every input regardless of outcome — no early termination, every promise reports back.
3
if (r.status === 'fulfilled') { ok.push(r.value); }
The literal-string discriminant lets TypeScript narrow `r` to the success arm, exposing `value: T` without a cast.
4
failed.push(r.reason);
Stays `unknown` deliberately — any consumer must type-guard before treating it as an `Error`.