Ultra‑small (no globals, no heavy polyfill machinery) subset of AbortController / AbortSignal for runtimes missing the standard.
Targets:
- QuickJS / embed engines
- WeChat Mini Programs
- Older Node (<14.17) or minimal sandboxes
Not a drop‑in polyfill. It’s intentionally smaller and simpler. Key differences from the web standard:
-
No global patching: this library never defines or mutates global AbortController/AbortSignal; import what you need.
AbortController→AbortControllerLite(interfaceAbortControllerLike).AbortSignal→AbortSignalLite(interfaceAbortSignalLike).
-
Event model
- No EventTarget. Listeners are plain functions, invoked as
listener.call(signal)with no Event object. - No
onabortproperty, no options (once,signal,capture, etc.) toaddEventListener, nodispatchEvent. - After the first abort we clear listeners to save memory; manual re‑dispatch isn’t supported. Effectively behaves like
{ once: true }because an abort happens at most once and listeners are removed.
- No EventTarget. Listeners are plain functions, invoked as
-
Errors and
reason- We use lightweight
Errorsubclasses (AbortError,TimeoutError), notDOMException. throwIfAborted()throws the storedreasonas‑is; avoid relying oninstanceof DOMException. Prefer checking by name (e.g.,err instanceof Error && err.name === 'AbortError').
- We use lightweight
-
AbortSignalLite.any(iterable)memory semantics- Immediate: if any input is already aborted, the result aborts immediately with that
reason. - Native engines maintain internal weak source/dependent lists so combined signals don’t keep inputs alive and vice‑versa.
- In pure JS we can’t mirror that precisely: because
WeakSetis not iterable, we must keep strong references to sources and dependents and unlink them carefully to avoid leaks. WhileWeakRefandFinalizationRegistrycould approximate an “iterable weak set”, they add complexity and overhead we avoid in this lightweight library, especially since those APIs are often unavailable or unreliable in our target environments. - Consequence: if you drop all references to the combined signal before any source aborts, the source signals still hold internal references that keep the combined signal (and its captured arrays) alive. Those references are only cleared when one of the sources aborts.
- Practical guidance: use
any()for short‑lived operations; avoid creating many long‑lived combined signals.
- Immediate: if any input is already aborted, the result aborts immediately with that
-
AbortSignalLite.timeout(ms)- Uses a regular
setTimeout; in Node it is not unref’d, so it can keep the event loop alive until it fires. If you needunref, prefer nativeAbortSignal.timeout(Node 18+) or roll your own per‑env helper.
- Uses a regular
-
Listener error handling
- If a listener throws, we rethrow it asynchronously (
setTimeout) to avoid breaking other listeners. - Contrast: DOM event dispatch continues other listeners and reports the error (it does not throw to the
dispatchEventcall site). Our rethrow is also non‑blocking for peers but surfaces as an async uncaught error; timing/visibility differs from browsers.
- If a listener throws, we rethrow it asynchronously (
-
Types and modules
- ESM‑only package. Use import syntax;
require()isn’t supported. - Public types are
AbortControllerLikeandAbortSignalLike— lightweight subsets of the standard API. A parameter typedAbortSignalLikeis type‑compatible with the nativeAbortSignal; this is compile‑time only and does not guarantee identical runtime behavior or capabilities. - TypeScript flags direct construction (
new AbortSignalLite()) as a type error, but nothing prevents it at runtime. The web standard throws here; we rely on discipline — stick to signals from the controller or the static helpers for the intended lifecycle.
- ESM‑only package. Use import syntax;
If you need exact spec behavior (DOMException types, EventTarget, GC semantics of any, timer unref, etc.), use the platform’s built‑ins where available.
MIT