0 Tage Serie
typescript / expert
Snippet

Cancellation komponieren mit AbortSignal.any und timeout

AbortSignal.timeout erzeugt ein Signal, das nach der angegebenen Frist automatisch abbricht; AbortSignal.any führt mehrere Signale zusammen, sodass das erste Abort gewinnt und das kombinierte Signal dessen Grund trägt. Mit try/catch übersetzt der Helper jeden Abbruch – ob Timeout oder externer Cancel – in einen einzigen typisierten Fehler, während echte Task-Fehler unverändert weiterfliegen. Der Task bekommt nur das kombinierte Signal und bleibt frei von Cancellation-Sanitärzeug.

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
class CancelledError extends Error {
constructor(public readonly reason: unknown) {
super('operation cancelled');
this.name = 'CancelledError';
}
}
async function withDeadline<T>(
task: (signal: AbortSignal) => Promise<T>,
ms: number,
external?: AbortSignal,
): Promise<T> {
const timer = AbortSignal.timeout(ms);
const signals = external ? [timer, external] : [timer];
const combined = AbortSignal.any(signals);
try {
return await task(combined);
} catch (err) {
if (combined.aborted) throw new CancelledError(combined.reason);
throw err;
}
}
const userAbort = new AbortController();
await withDeadline((s) => doWork(s), 500, userAbort.signal);
Erklärung
1
const timer = AbortSignal.timeout(ms);
Die statische Factory liefert ein selbst-abbrechendes Signal – ohne Controller oder setTimeout.
2
const combined = AbortSignal.any(signals);
Bricht ab, sobald irgendein Eingangssignal abbricht, und gibt dessen Grund weiter.
3
if (combined.aborted) throw new CancelledError(combined.reason);
Trennt einen abbruchbedingten Reject von einem echten Task-Fehler.
4
return await task(combined);
Der Task sieht nur ein Signal und kennt die Zusammensetzung der Cancellation nicht.
5
await withDeadline((s) => doWork(s), 500, userAbort.signal);
Der Aufrufer kombiniert seinen AbortController mit dem internen Timeout des Helpers.