Make AbortSignal a real constructor (prototype, throwIfAborted, events) (#267)
The AbortController/AbortSignal globals were minimal stubs: AbortSignal had no prototype and no throwIfAborted, and AbortController.abort() only set a flag without firing listeners. Pages that read AbortSignal.prototype or call throwIfAborted threw non-fatal TypeErrors. AbortSignal is now a class with a prototype, aborted/reason, throwIfAborted(), working addEventListener/onabort firing, and static abort()/timeout()/any(). AbortController.abort(reason) fires the abort event. Bootstrap-only, no perf impact.
This commit is contained in:
1 parent
2e82b6c85f
commit
6754c1caac
1 file changed
+84
-2
Vendored
+84
-2
@@ -3822,8 +3822,90 @@ globalThis.ToggleEvent = class ToggleEvent extends Event {
|
|||||||
};
|
};
|
||||||
_markNative(globalThis.ToggleEvent);
|
_markNative(globalThis.ToggleEvent);
|
||||||
|
|
||||||
globalThis.AbortController = class AbortController { constructor(){this.signal={aborted:false,addEventListener(){},removeEventListener(){},onabort:null};} abort(){this.signal.aborted=true;} };
|
// AbortController / AbortSignal. AbortSignal is a real constructor with a
|
||||||
globalThis.AbortSignal = { timeout(ms){return {aborted:false,addEventListener(){},removeEventListener(){}}; } };
|
// prototype, so feature-detection and `AbortSignal.prototype` access work. It
|
||||||
|
// carries aborted/reason, supports throwIfAborted(), and fires "abort" to
|
||||||
|
// onabort and addEventListener listeners when the controller aborts.
|
||||||
|
(function () {
|
||||||
|
const BRAND = Symbol("AbortSignal");
|
||||||
|
function emit(signal, evt) {
|
||||||
|
if (typeof signal.onabort === "function") {
|
||||||
|
try { signal.onabort.call(signal, evt); } catch (_) {}
|
||||||
|
}
|
||||||
|
for (const cb of signal._listeners.slice()) {
|
||||||
|
const fn = typeof cb === "function" ? cb : cb && cb.handleEvent;
|
||||||
|
if (typeof fn === "function") { try { fn.call(signal, evt); } catch (_) {} }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function fire(signal, reason) {
|
||||||
|
if (signal._aborted) return;
|
||||||
|
signal._aborted = true;
|
||||||
|
signal._reason = reason !== undefined
|
||||||
|
? reason
|
||||||
|
: new DOMException("signal is aborted without reason", "AbortError");
|
||||||
|
const evt = typeof Event === "function" ? new Event("abort") : { type: "abort" };
|
||||||
|
try { evt.target = signal; evt.currentTarget = signal; } catch (_) {}
|
||||||
|
emit(signal, evt);
|
||||||
|
}
|
||||||
|
globalThis.AbortSignal = class AbortSignal {
|
||||||
|
constructor(brand) {
|
||||||
|
if (brand !== BRAND) {
|
||||||
|
throw new TypeError("Failed to construct 'AbortSignal': Illegal constructor");
|
||||||
|
}
|
||||||
|
this._aborted = false;
|
||||||
|
this._reason = undefined;
|
||||||
|
this._listeners = [];
|
||||||
|
this.onabort = null;
|
||||||
|
}
|
||||||
|
get aborted() { return this._aborted; }
|
||||||
|
get reason() { return this._reason; }
|
||||||
|
throwIfAborted() { if (this._aborted) throw this._reason; }
|
||||||
|
addEventListener(type, cb) {
|
||||||
|
if (type === "abort" && cb != null) this._listeners.push(cb);
|
||||||
|
}
|
||||||
|
removeEventListener(type, cb) {
|
||||||
|
if (type !== "abort") return;
|
||||||
|
const i = this._listeners.indexOf(cb);
|
||||||
|
if (i >= 0) this._listeners.splice(i, 1);
|
||||||
|
}
|
||||||
|
dispatchEvent(evt) {
|
||||||
|
if (evt && evt.type === "abort") emit(this, evt);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
static abort(reason) {
|
||||||
|
const s = new AbortSignal(BRAND);
|
||||||
|
s._aborted = true;
|
||||||
|
s._reason = reason !== undefined
|
||||||
|
? reason
|
||||||
|
: new DOMException("signal is aborted without reason", "AbortError");
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
static timeout(ms) {
|
||||||
|
const s = new AbortSignal(BRAND);
|
||||||
|
setTimeout(() => fire(s, new DOMException("signal timed out", "TimeoutError")), ms);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
static any(signals) {
|
||||||
|
const s = new AbortSignal(BRAND);
|
||||||
|
const list = Array.from(signals || []);
|
||||||
|
for (const sig of list) {
|
||||||
|
if (sig && sig.aborted) { s._aborted = true; s._reason = sig.reason; return s; }
|
||||||
|
}
|
||||||
|
for (const sig of list) {
|
||||||
|
if (sig && typeof sig.addEventListener === "function") {
|
||||||
|
sig.addEventListener("abort", () => fire(s, sig.reason));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
globalThis.AbortController = class AbortController {
|
||||||
|
constructor() { this.signal = new globalThis.AbortSignal(BRAND); }
|
||||||
|
abort(reason) { fire(this.signal, reason); }
|
||||||
|
};
|
||||||
|
_markNative(globalThis.AbortSignal);
|
||||||
|
_markNative(globalThis.AbortController);
|
||||||
|
})();
|
||||||
// Normalize one Blob part to bytes. `native` newline normalization applies to
|
// Normalize one Blob part to bytes. `native` newline normalization applies to
|
||||||
// string parts when the Blob/File `endings` option is "native".
|
// string parts when the Blob/File `endings` option is "native".
|
||||||
function _blobPartToBytes(p, native) {
|
function _blobPartToBytes(p, native) {
|
||||||
|
|||||||
Reference in new issue
Block a user