Skip to content

Commit

Permalink
chore: change the abortsignal-ponyfill implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
l246804 committed Dec 9, 2024
1 parent 7026d33 commit 575383e
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 103 deletions.
6 changes: 3 additions & 3 deletions src/abortcontroller.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createAbortEvent, normalizeAbortReason } from "./abortsignal-ponyfill";
import { createAbortEvent, normalizeAbortReason } from './abortsignal-ponyfill';

class Emitter {
constructor() {
Expand Down Expand Up @@ -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);
}
Expand Down
65 changes: 6 additions & 59 deletions src/abortsignal-polyfill.js
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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);
192 changes: 151 additions & 41 deletions src/abortsignal-ponyfill.js
Original file line number Diff line number Diff line change
@@ -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<AbortSignal>} 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;
}

/**
Expand Down Expand Up @@ -108,8 +215,8 @@ export function createAbortEvent(reason) {
};
}
}
event.reason = reason
return event
event.reason = reason;
return event;
}

/**
Expand All @@ -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.
Expand All @@ -132,5 +242,5 @@ export function normalizeAbortReason(reason) {
}
}
}
return reason
return reason;
}
5 changes: 5 additions & 0 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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' &&
Expand Down

0 comments on commit 575383e

Please sign in to comment.