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);
|
||||
|
||||
globalThis.AbortController = class AbortController { constructor(){this.signal={aborted:false,addEventListener(){},removeEventListener(){},onabort:null};} abort(){this.signal.aborted=true;} };
|
||||
globalThis.AbortSignal = { timeout(ms){return {aborted:false,addEventListener(){},removeEventListener(){}}; } };
|
||||
// AbortController / AbortSignal. AbortSignal is a real constructor with a
|
||||
// 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
|
||||
// string parts when the Blob/File `endings` option is "native".
|
||||
function _blobPartToBytes(p, native) {
|
||||
|
||||
Reference in new issue
Block a user