From 575383ecb91a0f77a571b59e9c4e223832f032d9 Mon Sep 17 00:00:00 2001 From: Ray Hao <2468048176@qq.com> Date: Mon, 9 Dec 2024 11:25:52 +0800 Subject: [PATCH] chore: change the abortsignal-ponyfill implementation --- src/abortcontroller.js | 6 +- src/abortsignal-polyfill.js | 65 ++---------- src/abortsignal-ponyfill.js | 192 ++++++++++++++++++++++++++++-------- src/utils.js | 5 + 4 files changed, 165 insertions(+), 103 deletions(-) diff --git a/src/abortcontroller.js b/src/abortcontroller.js index 76fd4aa..f90d491 100644 --- a/src/abortcontroller.js +++ b/src/abortcontroller.js @@ -1,4 +1,4 @@ -import { createAbortEvent, normalizeAbortReason } from "./abortsignal-ponyfill"; +import { createAbortEvent, normalizeAbortReason } from './abortsignal-ponyfill'; class Emitter { constructor() { @@ -141,8 +141,8 @@ export class AbortController { Object.defineProperty(this, 'signal', { value: new AbortSignal(), writable: true, configurable: true }); } abort(reason) { - const signalReason = normalizeAbortReason(reason) - const event = createAbortEvent(signalReason) + const signalReason = normalizeAbortReason(reason); + const event = createAbortEvent(signalReason); this.signal.dispatchEvent(event); } diff --git a/src/abortsignal-polyfill.js b/src/abortsignal-polyfill.js index 3b13ba3..3d2a7e6 100644 --- a/src/abortsignal-polyfill.js +++ b/src/abortsignal-polyfill.js @@ -1,5 +1,5 @@ -import { abortsignalPonyfill, normalizeAbortReason, createAbortEvent } from "./abortsignal-ponyfill"; -import { signalPolyfillNeeded } from "./utils"; +import { AbortSignal, AbortController } from './abortsignal-ponyfill'; +import { signalPolyfillNeeded } from './utils'; (function (self) { 'use strict'; @@ -8,64 +8,11 @@ import { signalPolyfillNeeded } from "./utils"; return; } - self.AbortSignal.abort = function abort(reason) { - const ac = new AbortController() - ac.abort(reason) - return ac.signal + if (!self.AbortSignal.__polyfill__) { + self.AbortSignal = AbortSignal; } - self.AbortSignal.any = function any(iterable) { - const controller = new AbortController(); - /** - * @this AbortSignal - */ - function abort() { - controller.abort(this.reason); - clean(); - } - function clean() { - for (const signal of iterable) signal.removeEventListener('abort', abort); - } - for (const signal of iterable) - if (signal.aborted) { - controller.abort(signal.reason); - break; - } else signal.addEventListener('abort', abort); - - return controller.signal; - } - - self.AbortSignal.timeout = function timeout(ms) { - const controller = new AbortController(); - - setTimeout(() => controller.abort(new DOMException(`This signal is timeout in ${time}ms`, 'TimeoutError')), time); - - return controller.signal; - } - - const NativeAbortController = self.AbortController - if (!NativeAbortController.__polyfill__) { - self.AbortController = class AbortController extends NativeAbortController { - constructor() { - super() - abortsignalPonyfill(this.signal) - } - - static __polyfill__ = true - - abort(reason) { - if (!this.signal.aborted) { - super.abort(reason) - - if (this.signal.__ponyfill__) { - const signalReason = normalizeAbortReason(reason) - const event = createAbortEvent(signalReason) - - this.signal._reason = signalReason - this.signal.dispatchEvent(event) - } - } - } - } + if (!self.AbortController.__polyfill__) { + self.AbortController = AbortController; } })(typeof self !== 'undefined' ? self : global); diff --git a/src/abortsignal-ponyfill.js b/src/abortsignal-ponyfill.js index c331e31..fe55455 100644 --- a/src/abortsignal-ponyfill.js +++ b/src/abortsignal-ponyfill.js @@ -1,84 +1,191 @@ +const { NativeAbortSignal, NativeAbortController } = (function (self) { + return { + NativeAbortSignal: self.AbortSignal, + NativeAbortController: self.AbortController, + }; +})(typeof self !== 'undefined' ? self : global); + +export class AbortSignal extends NativeAbortSignal { + constructor() { + super(); + } + + /** + * polyfill flag + */ + static get __polyfill__() { + return true; + } + + /** + * @see {@link https://developer.mozilla.org/zh-CN/docs/Web/API/AbortSignal/abort_static} + * + * @param {any} reason The reason why the operation was aborted, which can be any JavaScript value. If not specified, the reason is set to "AbortError" {@link DOMException}. + * + * @returns {AbortSignal} An {@link AbortSignal} instance with the {@link AbortSignal.aborted} property set to `true`, and {@link AbortSignal.reason} set to the specified or default reason value. + */ + static abort(reason) { + const ac = new AbortController(); + ac.abort(reason); + return ac.signal; + } + + /** + * @see {@link https://developer.mozilla.org/zh-CN/docs/Web/API/AbortSignal/timeout_static} + * @param {number} time The "active" time in milliseconds before the returned {@link AbortSignal} will abort. + * The value must be within range of 0 and {@link Number.MAX_SAFE_INTEGER}. + * @returns {AbortSignal} The signal will abort with its {@link AbortSignal.reason} property set to a `TimeoutError` {@link DOMException} on timeout, + * or an `AbortError` {@link DOMException} if the operation was user-triggered. + */ + static timeout(time) { + const controller = new AbortController(); + + setTimeout(() => controller.abort(new DOMException(`This signal is timeout in ${time}ms`, 'TimeoutError')), time); + + return controller.signal; + } + + /** + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/any_static} + * + * @param {Iterable} iterable An {@link Iterable} (such as an {@link Array}) of abort signals. + * + * @returns {AbortSignal} - **Already aborted**, if any of the abort signals given is already aborted. + * The returned {@link AbortSignal}'s reason will be already set to the `reason` of the first abort signal that was already aborted. + * - **Asynchronously aborted**, when any abort signal in `iterable` aborts. + * The `reason` will be set to the reason of the first abort signal that is aborted. + */ + static any(iterable) { + const controller = new AbortController(); + /** + * @this AbortSignal + */ + function abort() { + controller.abort(this.reason); + clean(); + } + function clean() { + for (const signal of iterable) signal.removeEventListener('abort', abort); + } + for (const signal of iterable) + if (signal.aborted) { + controller.abort(signal.reason); + break; + } else signal.addEventListener('abort', abort); + + return controller.signal; + } +} + +export class AbortController extends NativeAbortController { + constructor() { + super(); + patchAbortSignal(this.signal); + } + + /** + * polyfill flag + */ + static get __polyfill__() { + return true; + } + + abort(reason) { + if (!this.signal.aborted) { + super.abort(reason); + + if (this.signal.__polyfill__) { + const signalReason = normalizeAbortReason(reason); + const event = createAbortEvent(signalReason); + + this.signal._reason = signalReason; + this.signal.dispatchEvent(event); + } + } + } +} + /** - * Make the native AbortSignal instances support the reason property and the throwIfAborted method. - * @param {AbortSignal} signal native AbortSignal instance - * @returns {AbortSignal} AbortSignal instance + * Make the native {@link AbortSignal} instances support the reason property and the throwIfAborted method. + * @param {AbortSignal} signal native {@link AbortSignal} instance + * @returns {AbortSignal} {@link AbortSignal} instance */ -export function abortsignalPonyfill(signal) { +function patchAbortSignal(signal) { if (!('reason' in signal)) { - signal._reason = undefined - signal._onabort = null + signal._reason = undefined; + signal._onabort = null; Object.defineProperties(signal, { - __ponyfill__: { + __polyfill__: { value: true, }, reason: { get() { - return this._reason + return this._reason; } }, onabort: { get() { - return this._onabort + return this._onabort; }, set(callback) { - const existing = this._onabort + const existing = this._onabort; if (existing) { - this.removeEventListener('abort', existing) + this.removeEventListener('abort', existing); } - this._onabort = callback - this.addEventListener('abort', callback) + this._onabort = callback; + this.addEventListener('abort', callback); } } - }) + }); - const { dispatchEvent, addEventListener, removeEventListener } = signal + const { dispatchEvent, addEventListener, removeEventListener } = signal; signal.addEventListener = function (type, callback, options) { - if (type === 'abort' && callback && this.__ponyfill__) { - if (!callback.__ponyfill__) { - const rawCallback = callback - Object.defineProperty(callback, '__ponyfill__', { + if (type === 'abort' && callback && this.__polyfill__) { + if (!callback.__polyfill__) { + const rawCallback = callback; + Object.defineProperty(callback, '__polyfill__', { value(e) { - if (e.__ponyfill__) { - return rawCallback.call(this, e) + if (e.__polyfill__) { + return rawCallback.call(this, e); } } - }) + }); } - callback = callback.__ponyfill__ + callback = callback.__polyfill__; } - return addEventListener.call(this, type, callback, options) - } + return addEventListener.call(this, type, callback, options); + }; signal.removeEventListener = function (type, callback, options) { - if (type === 'abort' && callback && this.__ponyfill__ && callback.__ponyfill__) { - callback = callback.__ponyfill__ + if (type === 'abort' && callback && this.__polyfill__ && callback.__polyfill__) { + callback = callback.__polyfill__; } - return removeEventListener.call(this, type, callback, options) - } + return removeEventListener.call(this, type, callback, options); + }; signal.dispatchEvent = function (event) { if (event.type === 'abort') { - Object.defineProperty(event, '__ponyfill__', { + Object.defineProperty(event, '__polyfill__', { value: true, - }) + }); } - return dispatchEvent.call(this, event) - } + return dispatchEvent.call(this, event); + }; } - if (!('throwIfAborted') in signal) { + if (!('throwIfAborted' in signal)) { signal.throwIfAborted = function throwIfAborted() { if (this.aborted) { - throw this.reason + throw this.reason; } - } + }; } - return signal + return signal; } /** @@ -108,8 +215,8 @@ export function createAbortEvent(reason) { }; } } - event.reason = reason - return event + event.reason = reason; + return event; } /** @@ -123,7 +230,10 @@ export function normalizeAbortReason(reason) { } else { try { reason = new DOMException('signal is aborted without reason'); - reason.name = 'AbortError'; + // The DOMException does not support setting the name property directly. + Object.defineProperty(reason, 'name', { + value: 'AbortError' + }); } catch (err) { // IE 11 does not support calling the DOMException constructor, use a // regular error object on it instead. @@ -132,5 +242,5 @@ export function normalizeAbortReason(reason) { } } } - return reason + return reason; } diff --git a/src/utils.js b/src/utils.js index b1f2c37..1069244 100644 --- a/src/utils.js +++ b/src/utils.js @@ -17,6 +17,11 @@ export function polyfillNeeded(self) { } export function signalPolyfillNeeded(self) { + if (self.__FORCE_INSTALL_ABORTCONTROLLER_POLYFILL) { + console.log('__FORCE_INSTALL_ABORTCONTROLLER_POLYFILL=true is set, will force install polyfill'); + return true; + } + return ( !!self.AbortController && typeof self.AbortSignal === 'function' &&