WHATWG DOM Abort
Elide implements the WHATWG [DOM Living Standard][spec] abort interfaces as globals:
AbortController and AbortSignal. They integrate with EventTarget — AbortSignal.prototype
inherits from EventTarget.prototype, and addEventListener(..., { signal }) removes the
listener automatically when the signal aborts.
[spec]: https://dom.spec.whatwg.org/#aborting-ongoing-activities
AbortController
js
Calling new AbortController()AbortController(...) without new throws TypeError — per WebIDL, interface objects
with constructors must be invoked with new.
| Member | Type | Notes |
|---|---|---|
signal | AbortSignal | Readonly, [SameObject] — repeated reads return the same signal instance. |
abort(reason?) | undefined | Marks signal as aborted with reason and dispatches a trusted abort event. Idempotent — later calls are no-ops. When reason is omitted, a fresh new DOMException("The operation was aborted", "AbortError") is stored (WebIDL signature is new DOMException(message, name)). |
AbortSignal
new AbortSignal() always throws TypeError — signals are only created via AbortController,
AbortSignal.abort(...), or AbortSignal.any(...).
| Member | Type | Notes |
|---|---|---|
aborted | boolean | Readonly; true once abort(...) has been called. |
reason | any | Readonly; the value passed to abort(...), or a default DOMException("AbortError"). |
throwIfAborted() | undefined | Throws reason if aborted is true; otherwise a no-op. |
onabort | EventHandler | Get/set the single abort event handler. Setting null or a non-callable clears it; setting a new function replaces the previous handler. |
addEventListener / removeEventListener / dispatchEvent | inherited | Inherited from EventTarget.prototype — the signal is itself an EventTarget. |
Static factories
| Method | Type | Notes |
|---|---|---|
AbortSignal.abort(reason?) | AbortSignal | Returns a fresh, already-aborted signal. |
AbortSignal.any(signals) | AbortSignal | Returns a signal that aborts as soon as any input signal aborts. Inputs must be AbortSignals; if any is already aborted when called, the returned signal is aborted before return. |
AbortSignal.timeout(ms) | AbortSignal | Returns a fresh signal that aborts after ms milliseconds with a TimeoutError DOMException (distinct from the AbortError used by AbortController.abort()). The argument follows WebIDL [EnforceRange] unsigned long long semantics with a practical cap at Number.MAX_SAFE_INTEGER: NaN, ±Infinity, negative values, and values above the cap throw TypeError. Fractional inputs are truncated toward zero (1.5 → 1). |
Integration with addEventListener
The third argument to EventTarget.addEventListener(type, callback, options) accepts a
{ signal } property. When present:
- If the signal is already aborted, the listener is not registered.
- Otherwise the listener is added normally, and the signal arranges for the listener to be removed automatically when the signal aborts.
js
const target = new EventTarget();
const ctrl = new AbortController();
target.addEventListener("message", onMessage, { signal: ctrl.signal });
// Later — removes the listener, no matter who registered it:
ctrl.abort();Non-AbortSignal values passed as signal are ignored (they behave as if the property were
absent), matching Node.js and browsers.
Example
js
// AbortSignal.timeout produces a fresh signal that aborts after the given delay.
async function fetchWithTimeout(url, ms) {
return fetch(url, { signal: AbortSignal.timeout(ms) });
}
// Compose with another cancellation source — typing Ctrl-C, parent shutdown, etc.
const any = AbortSignal.any([userCancel.signal, AbortSignal.timeout(5000)]);Notes on the implementation
- Plain-call
AbortController(...)is routed toTypeErrorvia a manualconstruct?-ternary patch in the generatedJSAbortControllerClassBase.java(same pattern asDOMException, the encoding classes and the event classes). The upstreamelide-dev/bindgengenerator emits this branch automatically on the next regenerate.AbortSignal's "not directly constructible"TypeErroris already present in the generated base. - The two classes skip the generated
putBuiltinClassand use a custominstallAsGlobalin the agent. The generated path consumes a slot in the realm's sharedJSObjectFactory.IntrinsicBuildershape array — that array is sized atJSContextcreation from graal-js's own factory count with no public extension point, and non-ordinary subclass layouts (AbortSignalObject/AbortControllerObjectextendJSNonProxyObject) collide with graal-js's ownJSOrdinaryObjectshapes at high slots.installAsGlobalsidesteps that collision by building the factory viaJSObjectFactory.createBound, which keeps the shape on the factory itself rather than in the realm-shared array. - The
signaloption onaddEventListeneris parsed byEventsOps.parseListenerOptionsand consumed byJSEventTarget.addListenerthrough the publicAbortOps.registerRemoveOnAborthook, which callsJSEventTarget.removeListenerByReferencefrom the signal's abort-cleanup list. No public API change to theEventTargetcontract; behaviour is additive.