demo · v131
Timeout Scope
Combine AbortSignal.timeout() with await using so a fetch and all its cascading cleanup are tied to a single scope. Left panel: request completes before the deadline — the scope exits cleanly. Right panel: request exceeds the timeout — the AbortController fires, fetch is cancelled, and the resource stack unwinds automatically.
✓ completes in time
elapsed
—
ready
✕ exceeds timeout
elapsed
—
ready
AbortSignal.any(): timeout or user cancel
composed signal state
ready
click run, then either wait for timeout or cancel manually
—adjust sliders and click "Run both scenarios"
the code
async function fetchWithTimeout(url, ms) {
// The DisposableStack unwinds automatically when the scope exits
await using stack = new AsyncDisposableStack();
// AbortController registered as a disposable resource
const controller = new AbortController();
stack.defer(async () => {
controller.abort(); // called even if fetch throws
console.log("AbortController cleaned up");
});
// Race fetch against a timeout signal
const timeoutSignal = AbortSignal.timeout(ms);
const anySignal = AbortSignal.any([controller.signal, timeoutSignal]);
const res = await fetch(url, { signal: anySignal });
const data = await res.json();
return data; // stack.disposeAsync() runs here → controller.abort()
}
// Usage
try {
const data = await fetchWithTimeout("/api/slow", 3000);
} catch (e) {
if (e.name === "TimeoutError") console.log("timed out — all resources disposed");
}
see also
- Explicit resource management (async) — feature index
- AbortController scope
- Component Lifecycle — observer + timer cleanup
- ChromeStatus entry