Upgrading from 1.x
2.0 is a redesign around the native AbortSignal. In 1.x, controller.signal was a wrapper class (EnhancedAbortSignal) that kept the real signal in a private field. Platform APIs don’t accept the wrapper, and the documented fetch(url, { signal: controller.signal.signal }) read a property that didn’t exist: fetch got undefined and the request was never cancellable. In 2.0 there is no wrapper.
Before and after
Section titled “Before and after”| 1.x | 2.0 |
|---|---|
fetch(url, { signal: controller.signal.signal }) |
fetch(url, { signal: controller.signal }) |
controller.signal is an EnhancedAbortSignal wrapper, a new object on every read |
controller.signal is the native AbortSignal, always the same object |
signal.throwIfAborted('message') throws new AbortError(message) |
Native signal.throwIfAborted() throws signal.reason |
catch (e) { if (e instanceof AbortError) ... } |
catch (e) { if (isAbortError(e)) ... } (fetch throws DOMExceptions, not the library’s class) |
Linked and any signals abort with no reason (the parent’s reason is dropped) |
The parent’s reason is forwarded, the same object |
register(() => ...) |
register((reason) => ...), returns an AbortRegistration with using support |
signal.whenAborted resolves with undefined |
Resolves with the reason |
Timeout reason: AbortError |
TimeoutError DOMException, like AbortSignal.timeout |
abortAfter(ms) returns void |
Returns the controller (chainable); accepts a TimeSpan |
abortAfter(NaN) / negative fires at once |
Throws RangeError; Infinity / -1 cancel the schedule |
Delays over 24.8 days fire immediately (setTimeout overflow) |
Work (chained timers) |
abortAfter timer keeps Node running until it fires |
Unref’d, like AbortSignal.timeout |
dispose() documented as aborting; abort() after dispose() silently ignored |
dispose() releases the timer and links and does not abort; abort() still works; abortAfter() after dispose throws |
| Linked controllers keep their listeners on the parents forever | Removed when the child aborts or is disposed |
EnhancedAbortSignal.none / .aborted: wrapper objects, none a new one each read |
none is one shared native signal; aborted a new native aborted signal |
EnhancedAbortSignal.timeout(ms) timer kept the process alive, AbortError reason |
Native AbortSignal.timeout semantics, accepts TimeSpan, Infinity → none |
EnhancedAbortSignal.any(signals) takes wrappers |
Takes any AbortSignals (and skips undefined, null, none) |
signal.isAborted, signal.canBeAborted on the wrapper |
signal.aborted (native); signal.canBeAborted on a controller’s signal, EnhancedAbortSignal.canBeAborted(signal) for any |
No using |
using for controllers and registrations |
| CommonJS only | ESM and CommonJS |
Unchanged: new EnhancedAbortController(), abort(reason?), isAborted, reason, isDisposed, abortAfterTimeSpan, EnhancedAbortController.timeout, createLinkedController / linkSignals, TimeSpan factories and total* getters, AbortError.
Checklist
Section titled “Checklist”-
Replace
.signal.signalwith.signaleverywhere. Search forsignal.signal. -
Stop wrapping and unwrapping. Remove
new EnhancedAbortSignal(...)calls; 2.0’sEnhancedAbortSignalis a helper object, not a class. Functions that took anEnhancedAbortSignalshould take anAbortSignal. -
Replace
instanceof AbortErrorwithisAbortError(e), and useisTimeoutError(e)where you tell timeouts from cancels. fetch rejects withDOMExceptions that were neverinstanceofthe library’s class. -
Replace
signal.throwIfAborted('message')withsignal.throwIfAborted(). To get a specific error, abort with it:controller.abort(new AbortError('message')). -
Check code that reads reasons. Linked controllers now forward the parent’s reason; timeouts are
TimeoutError;whenAbortedresolves with the reason. Code that compared reasons to strings or expectedundefinedmay need updating. -
Check
dispose()calls. If you relied ondispose()to stop work, callabort()first (or instead). If you relied onabort()being ignored afterdispose(), guard it. -
Check scripts that only wait for a deadline. The timer no longer keeps Node alive; see abortAfter & timeouts.
-
Validate delays.
abortAfter(NaN)and negative delays now throw.abortAfter(-1)means “no deadline”. -
Optionally adopt
usingfor controllers and registrations, and dispose linked controllers so their parent listeners go away.