From 58442fb454d5933c98aabe6761ce8aa11c6c810b Mon Sep 17 00:00:00 2001 From: farfromrefuge Date: Wed, 11 Aug 2021 20:17:13 +0200 Subject: [PATCH] feat: AbortController polyfill (#9333) --- packages/core/abortcontroller/abortsignal.ts | 80 ++++++++++++++++++++ packages/core/abortcontroller/index.ts | 62 +++++++++++++++ packages/core/globals/index.ts | 3 + 3 files changed, 145 insertions(+) create mode 100644 packages/core/abortcontroller/abortsignal.ts create mode 100644 packages/core/abortcontroller/index.ts diff --git a/packages/core/abortcontroller/abortsignal.ts b/packages/core/abortcontroller/abortsignal.ts new file mode 100644 index 000000000..07d0c3d8a --- /dev/null +++ b/packages/core/abortcontroller/abortsignal.ts @@ -0,0 +1,80 @@ + +import { Observable } from '../data/observable'; + +// Known Limitation +// Use `any` because the type of `AbortSignal` in `lib.dom.d.ts` is wrong and +// to make assignable our `AbortSignal` into that. +// https://github.com/Microsoft/TSJS-lib-generator/pull/623 +type Events = { + abort: any // Event & Type<"abort"> +} +type EventAttributes = { + onabort: any // Event & Type<"abort"> +} + +/** + * The signal class. + * @see https://dom.spec.whatwg.org/#abortsignal + */ +export default class AbortSignal extends Observable { + /** + * AbortSignal cannot be constructed directly. + */ + public constructor() { + super() + } + + /** + * Returns `true` if this `AbortSignal`'s `AbortController` has signaled to abort, and `false` otherwise. + */ + public get aborted(): boolean { + const aborted = abortedFlags.get(this) + if (typeof aborted !== "boolean") { + throw new TypeError( + `Expected 'this' to be an 'AbortSignal' object, but got ${ + this === null ? "null" : typeof this + }`, + ) + } + return aborted + } +} + +/** + * Create an AbortSignal object. + */ +export function createAbortSignal(): AbortSignal { + const signal = new AbortSignal(); + abortedFlags.set(signal, false) + return signal +} + +/** + * Abort a given signal. + */ +export function abortSignal(signal: AbortSignal): void { + if (abortedFlags.get(signal) !== false) { + return + } + + abortedFlags.set(signal, true) + signal.notify({ eventName: "abort", type: "abort" }) +} + +/** + * Aborted flag for each instances. + */ +const abortedFlags = new WeakMap() + +// Properties should be enumerable. +Object.defineProperties(AbortSignal.prototype, { + aborted: { enumerable: true }, +}) + +// `toString()` should return `"[object AbortSignal]"` +if (typeof Symbol === "function" && typeof Symbol.toStringTag === "symbol") { + Object.defineProperty(AbortSignal.prototype, Symbol.toStringTag, { + configurable: true, + value: "AbortSignal", + }) +} \ No newline at end of file diff --git a/packages/core/abortcontroller/index.ts b/packages/core/abortcontroller/index.ts new file mode 100644 index 000000000..9fc8e318e --- /dev/null +++ b/packages/core/abortcontroller/index.ts @@ -0,0 +1,62 @@ +import AbortSignal, { abortSignal, createAbortSignal } from "./abortsignal" +/** + * The AbortController. + * @see https://dom.spec.whatwg.org/#abortcontroller + */ +export default class AbortController { + /** + * Initialize this controller. + */ + public constructor() { + signals.set(this, createAbortSignal()) + } + + /** + * Returns the `AbortSignal` object associated with this object. + */ + public get signal(): AbortSignal { + return getSignal(this) + } + + /** + * Abort and signal to any observers that the associated activity is to be aborted. + */ + public abort(): void { + abortSignal(getSignal(this)) + } +} + +/** + * Associated signals. + */ +const signals = new WeakMap() + +/** + * Get the associated signal of a given controller. + */ +function getSignal(controller: AbortController): AbortSignal { + const signal = signals.get(controller) + if (signal == null) { + throw new TypeError( + `Expected 'this' to be an 'AbortController' object, but got ${ + controller === null ? "null" : typeof controller + }`, + ) + } + return signal +} + +// Properties should be enumerable. +Object.defineProperties(AbortController.prototype, { + signal: { enumerable: true }, + abort: { enumerable: true }, +}) + +if (typeof Symbol === "function" && typeof Symbol.toStringTag === "symbol") { + Object.defineProperty(AbortController.prototype, Symbol.toStringTag, { + configurable: true, + value: "AbortController", + }) +} + +export { AbortController, AbortSignal } \ No newline at end of file diff --git a/packages/core/globals/index.ts b/packages/core/globals/index.ts index 95d6fc2b5..c2f07e34a 100644 --- a/packages/core/globals/index.ts +++ b/packages/core/globals/index.ts @@ -319,6 +319,9 @@ export function initGlobal() { global.registerModule('fetch', () => require('../fetch')); installPolyfills('fetch', ['fetch', 'Headers', 'Request', 'Response']); + global.registerModule('abortcontroller', () => require('../abortcontroller')); + installPolyfills('abortcontroller', ['AbortController', 'AbortSignal']); + // Custom decorators global.Deprecated = function (target: Object, key?: string | symbol, descriptor?: any) {