-
Notifications
You must be signed in to change notification settings - Fork 25.2k
Replace abort-controller that has no update 7 years with React Native fork
#57230
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| /** | ||
| * Based on abort-controller by Toru Nagashima | ||
| * https://github.com/mysticatea/abort-controller | ||
| * | ||
| * Original work Copyright (c) 2017 Toru Nagashima | ||
| * Modified work Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| * of this software and associated documentation files (the "Software"), to deal | ||
| * in the Software without restriction, including without limitation the rights | ||
| * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| * copies of the Software, and to permit persons to whom the Software is | ||
| * furnished to do so, subject to the following conditions: | ||
| * | ||
| * The above copyright notice and this permission notice shall be included in all | ||
| * copies or substantial portions of the Software. | ||
| * | ||
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| * SOFTWARE. | ||
| * | ||
| * @flow strict | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should have a proper license here. Given that you copied the implementation from an external package, you should keep the original license in the header for this file. Something like:
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. updated |
||
| * @format | ||
| */ | ||
|
|
||
| // flowlint unsafe-getters-setters:off | ||
|
|
||
| import {AbortSignal, abortSignal, createAbortSignal} from './AbortSignal'; | ||
|
|
||
| const SIGNAL_KEY: symbol = Symbol('aborted'); | ||
|
|
||
| /** | ||
| * The AbortController. | ||
| * @see https://dom.spec.whatwg.org/#abortcontroller | ||
| */ | ||
| export class AbortController { | ||
| /** | ||
| * Initialize this controller. | ||
| */ | ||
| // $FlowExpectedError[unsupported-syntax] | ||
| [SIGNAL_KEY]: AbortSignal; | ||
|
|
||
| constructor() { | ||
| // $FlowExpectedError[prop-missing] | ||
| this[SIGNAL_KEY] = createAbortSignal(); | ||
| } | ||
|
|
||
| /** | ||
| * Returns the `AbortSignal` object associated with this object. | ||
| */ | ||
| get signal(): AbortSignal { | ||
| return getSignal(this); | ||
| } | ||
|
|
||
| /** | ||
| * Abort and signal to any observers that the associated activity is to be aborted. | ||
| */ | ||
| abort(reason: unknown): void { | ||
| abortSignal(reason, getSignal(this)); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Get the associated signal of a given controller. | ||
| */ | ||
| function getSignal(controller: AbortController): AbortSignal { | ||
| // $FlowExpectedError[prop-missing] | ||
| const signal = controller[SIGNAL_KEY]; | ||
| if (signal == null) { | ||
| throw new TypeError( | ||
| `Expected 'this' to be an 'AbortController' object, but got ${ | ||
| // $FlowExpectedError[invalid-compare] | ||
| controller === null ? 'null' : typeof controller | ||
| }`, | ||
| ); | ||
| } | ||
| return signal; | ||
| } | ||
|
|
||
| // Properties should be enumerable. | ||
| //$FlowExpectedError[cannot-write] | ||
| Object.defineProperties(AbortController.prototype, { | ||
| signal: {enumerable: true}, | ||
| abort: {enumerable: true}, | ||
| }); | ||
|
|
||
| //$FlowExpectedError[cannot-write] | ||
| Object.defineProperty(AbortController.prototype, Symbol.toStringTag, { | ||
| configurable: true, | ||
| value: 'AbortController', | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,236 @@ | ||
| /** | ||
| * Based on abort-controller by Toru Nagashima | ||
| * https://github.com/mysticatea/abort-controller | ||
| * | ||
| * Original work Copyright (c) 2017 Toru Nagashima | ||
| * Modified work Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| * of this software and associated documentation files (the "Software"), to deal | ||
| * in the Software without restriction, including without limitation the rights | ||
| * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| * copies of the Software, and to permit persons to whom the Software is | ||
| * furnished to do so, subject to the following conditions: | ||
| * | ||
| * The above copyright notice and this permission notice shall be included in all | ||
| * copies or substantial portions of the Software. | ||
| * | ||
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| * SOFTWARE. | ||
| * | ||
| * @flow strict | ||
| * @format | ||
| */ | ||
|
|
||
| // flowlint unsafe-getters-setters:off | ||
|
|
||
| import type {EventCallback} from '../events/EventTarget'; | ||
|
|
||
| import DOMException from '../../errors/DOMException'; | ||
| import Event from '../events/Event'; | ||
| import { | ||
| getEventHandlerAttribute, | ||
| setEventHandlerAttribute, | ||
| } from '../events/EventHandlerAttributes'; | ||
| import EventTarget from '../events/EventTarget'; | ||
| import {AbortController} from './AbortController'; | ||
|
|
||
| const ABORTED_KEY: symbol = Symbol('aborted'); | ||
| const REASON_KEY: symbol = Symbol('reason'); | ||
|
|
||
| /** | ||
| * The signal class. | ||
| * @see https://dom.spec.whatwg.org/#abortsignal | ||
| */ | ||
| export class AbortSignal extends EventTarget { | ||
| /** | ||
| * | ||
| * Returns an AbortSignal instance whose abort reason is set to reason if not undefined; otherwise to an "AbortError" DOMException. | ||
| * Docs: https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/abort_static | ||
| * Spec: https://dom.spec.whatwg.org/#dom-abortsignal-abort | ||
| */ | ||
| static abort(reason: unknown): AbortSignal { | ||
| const signal = createAbortSignal(); | ||
| abortSignal(reason, signal); | ||
| return signal; | ||
| } | ||
|
|
||
| /** | ||
| * AbortSignal.timeout static method | ||
| * Docs: https:developer.mozilla.org/en-US/docs/Web/API/AbortSignal/timeout_static | ||
| * Spec: https://dom.spec.whatwg.org/#dom-abortsignal-timeout | ||
| */ | ||
| static timeout(timeInMs: number): AbortSignal { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should also have a static
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you are right, I missed it |
||
| if (!(timeInMs >= 0)) { | ||
| throw new TypeError( | ||
| "Failed to execute 'timeout' on 'AbortSignal': The provided value has to be a non-negative number.", | ||
| ); | ||
| } | ||
| const controller = new AbortController(); | ||
| setTimeout( | ||
| () => | ||
| controller.abort(new DOMException('signal timed out', 'TimeoutError')), | ||
| timeInMs, | ||
| ); | ||
| return controller.signal; | ||
| } | ||
|
|
||
| /** | ||
| * 3. AbortSignal.any static method | ||
| * Docs: https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/any_static | ||
| * Spec: https://dom.spec.whatwg.org/#dom-abortsignal-any | ||
| */ | ||
| static any(signals: AbortSignal[]): AbortSignal { | ||
| if (!Array.isArray(signals)) { | ||
| throw new TypeError('The signals value must be an instance of Array'); | ||
| } | ||
|
|
||
| const controller = new AbortController(); | ||
| const listeners = []; | ||
| const cleanup = () => listeners.forEach(unsubscribe => unsubscribe()); | ||
|
|
||
| for (let i = 0; i < signals.length; i++) { | ||
| const signal = signals[i]; | ||
|
|
||
| // Validate that each item is an AbortSignal | ||
| if (!(signal instanceof AbortSignal)) { | ||
| cleanup(); // Remove all listeners added so far | ||
| throw new Error( | ||
| 'The "signals[' + | ||
| i + | ||
| ']" argument must be an instance of AbortSignal', | ||
| ); | ||
| } | ||
|
|
||
| // Abort immediately if one of the signals is already aborted | ||
| if (signal.aborted) { | ||
| cleanup(); // Remove all listeners added so far | ||
| controller.abort(signal.reason); | ||
| break; | ||
| } | ||
|
|
||
| const onAbort = () => { | ||
| controller.abort(signal.reason); | ||
| cleanup(); | ||
| }; | ||
| signal.addEventListener('abort', onAbort); | ||
| listeners.push(() => signal.removeEventListener('abort', onAbort)); | ||
| } | ||
| return controller.signal; | ||
| } | ||
|
|
||
| // $FlowExpectedError[unsupported-syntax] | ||
| [ABORTED_KEY]: boolean = false; | ||
| // $FlowExpectedError[unsupported-syntax] | ||
| [REASON_KEY]: unknown; | ||
|
|
||
| /** | ||
| * Returns `true` if this `AbortSignal`'s `AbortController` has signaled to abort, and `false` otherwise. | ||
| */ | ||
| get aborted(): boolean { | ||
| // $FlowExpectedError[prop-missing] | ||
| const aborted = this[ABORTED_KEY]; | ||
| if (typeof aborted !== 'boolean') { | ||
| throw new TypeError( | ||
| `Expected 'this' to be an 'AbortSignal' object, but got ${ | ||
| // $FlowExpectedError[invalid-compare] | ||
| this === null ? 'null' : typeof this | ||
| }`, | ||
| ); | ||
| } | ||
| return aborted; | ||
| } | ||
|
|
||
| get reason(): unknown { | ||
| // $FlowExpectedError[prop-missing] | ||
| return this[REASON_KEY]; | ||
| } | ||
|
|
||
| get onabort(): EventCallback | null { | ||
| return getEventHandlerAttribute(this, 'abort'); | ||
| } | ||
|
|
||
| set onabort(listener: ?EventCallback): void { | ||
| setEventHandlerAttribute(this, 'abort', listener); | ||
| } | ||
|
|
||
| throwIfAborted(): void { | ||
| if (this.aborted) { | ||
| throw this.reason; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Create an AbortSignal object. | ||
| */ | ||
| export function createAbortSignal(): AbortSignal { | ||
| return new AbortSignal(); | ||
| } | ||
|
|
||
| /** | ||
| * Abort a given signal. | ||
| */ | ||
| export function abortSignal( | ||
| reason: unknown | void = new DOMException( | ||
| 'signal is aborted without reason', | ||
| 'AbortError', | ||
| ), | ||
| signal: AbortSignal, | ||
| ): void { | ||
| // $FlowExpectedError[invalid-compare] | ||
| // $FlowExpectedError[prop-missing] | ||
| if (signal[ABORTED_KEY] !== false) { | ||
| return; | ||
| } | ||
| // $FlowExpectedError[prop-missing] | ||
| signal[ABORTED_KEY] = true; | ||
| // $FlowExpectedError[prop-missing] | ||
| signal[REASON_KEY] = reason; | ||
| signal.dispatchEvent(new Event('abort')); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be a trusted event, so it should be dispatched via |
||
| } | ||
|
|
||
| // Properties should be enumerable. | ||
| //$FlowExpectedError[cannot-write] | ||
| Object.defineProperties(AbortSignal.prototype, { | ||
| aborted: {enumerable: true}, | ||
| reason: {enumerable: true}, | ||
| onabort: {enumerable: true}, | ||
| throwIfAborted: {enumerable: true}, | ||
| }); | ||
|
|
||
| // `toString()` should return `"[object AbortSignal]"` | ||
| Object.defineProperty(AbortSignal.prototype, Symbol.toStringTag, { | ||
| configurable: true, | ||
| value: 'AbortSignal', | ||
| }); | ||
|
|
||
| /** | ||
| * AbortSignal cannot be constructed directly. | ||
| * So this wrapper is used to achieve such behavior | ||
| */ | ||
| export const AbortSignal_public: typeof AbortSignal = | ||
| /* eslint-disable no-shadow */ | ||
| // $FlowExpectedError[incompatible-type] | ||
| function AbortSignal() { | ||
| throw new TypeError( | ||
| "Failed to construct 'AbortSignal': Illegal constructor", | ||
| ); | ||
| }; | ||
|
|
||
| // Copy static properties ('length', 'name', 'prototype', 'abort', 'any', 'timeout') so that callers accessing them via the public constructor (e.g. `AbortSignal.timeout(0)`) still work. | ||
| // $FlowFixMe[unsafe-object-assign] | ||
| // $FlowFixMe[not-an-object] | ||
| Object.getOwnPropertyNames(AbortSignal).forEach(methodName => { | ||
| Object.defineProperty( | ||
| AbortSignal_public, | ||
| methodName, | ||
| // $FlowExpectedError[incompatible-type] | ||
| Object.getOwnPropertyDescriptor(AbortSignal, methodName), | ||
| ); | ||
| }); | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RN uses a public interface , same as tests do
https://github.com/react/react-native/pull/57230/changes#r3491521247