11/**
2+ * Based on abort-controller by Toru Nagashima
3+ * https://github.com/mysticatea/abort-controller
4+ *
5+ * Original work Copyright (c) 2017 Toru Nagashima
6+ * Modified work Copyright (c) Meta Platforms, Inc. and affiliates.
7+ *
8+ * Permission is hereby granted, free of charge, to any person obtaining a copy
9+ * of this software and associated documentation files (the "Software"), to deal
10+ * in the Software without restriction, including without limitation the rights
11+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+ * copies of the Software, and to permit persons to whom the Software is
13+ * furnished to do so, subject to the following conditions:
14+ *
15+ * The above copyright notice and this permission notice shall be included in all
16+ * copies or substantial portions of the Software.
17+ *
18+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24+ * SOFTWARE.
25+ *
226 * @flow strict
327 * @format
428 */
5- import Event from '../events/Event' ;
6- import EventTarget from '../events/EventTarget'
729
30+ // flowlint unsafe-getters-setters:off
31+
32+ import type { EventCallback } from '../events/EventTarget' ;
33+
34+ import DOMException from '../../errors/DOMException' ;
35+ import Event from '../events/Event' ;
36+ import {
37+ getEventHandlerAttribute ,
38+ setEventHandlerAttribute ,
39+ } from '../events/EventHandlerAttributes' ;
40+ import EventTarget from '../events/EventTarget' ;
41+ import { AbortController } from './AbortController' ;
842
43+ const reasons = new WeakMap < AbortSignal , unknown > ( ) ;
944
1045/**
1146 * The signal class.
1247 * @see https://dom.spec.whatwg.org/#abortsignal
1348 */
1449export class AbortSignal extends EventTarget {
50+ /**
51+ *
52+ * Returns an AbortSignal instance whose abort reason is set to reason if not undefined; otherwise to an "AbortError" DOMException.
53+ * Docs: https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/abort_static
54+ * Spec: https://dom.spec.whatwg.org/#dom-abortsignal-abort
55+ */
56+ static abort ( reason : unknown ) : AbortSignal {
57+ const signal = createAbortSignal ( ) ;
58+ abortSignal ( reason , signal ) ;
59+ return signal ;
60+ }
61+
62+ /**
63+ * AbortSignal.timeout static method
64+ * Docs: https:developer.mozilla.org/en-US/docs/Web/API/AbortSignal/timeout_static
65+ * Spec: https://dom.spec.whatwg.org/#dom-abortsignal-timeout
66+ */
67+ static timeout ( timeInMs : number ) : AbortSignal {
68+ if ( ! ( timeInMs >= 0 ) ) {
69+ throw new TypeError (
70+ "Failed to execute 'timeout' on 'AbortSignal': The provided value has to be a non-negative number." ,
71+ ) ;
72+ }
73+ const controller = new AbortController ( ) ;
74+ setTimeout (
75+ ( ) =>
76+ controller . abort ( new DOMException ( 'signal timed out' , 'TimeoutError' ) ) ,
77+ timeInMs ,
78+ ) ;
79+ return controller . signal ;
80+ }
81+
82+ /**
83+ * 3. AbortSignal.any static method
84+ * Docs: https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/any_static
85+ * Spec: https://dom.spec.whatwg.org/#dom-abortsignal-any
86+ */
87+ static any ( signals : AbortSignal [ ] ) : AbortSignal {
88+ if ( ! Array . isArray ( signals ) ) {
89+ throw new TypeError ( 'The signals value must be an instance of Array' ) ;
90+ }
91+
92+ const controller = new AbortController ( ) ;
93+ const listeners = [ ] ;
94+ const cleanup = ( ) => listeners . forEach ( unsubscribe => unsubscribe ( ) ) ;
95+
96+ for ( let i = 0 ; i < signals . length ; i ++ ) {
97+ const signal = signals [ i ] ;
98+
99+ // Validate that each item is an AbortSignal
100+ if ( ! ( signal instanceof AbortSignal ) ) {
101+ cleanup ( ) ; // Remove all listeners added so far
102+ throw new Error (
103+ 'The "signals[' +
104+ i +
105+ ']" argument must be an instance of AbortSignal' ,
106+ ) ;
107+ }
108+
109+ // Abort immediately if one of the signals is already aborted
110+ if ( signal . aborted ) {
111+ cleanup ( ) ; // Remove all listeners added so far
112+ controller . abort ( signal . reason ) ;
113+ break ;
114+ }
115+
116+ const onAbort = ( ) => {
117+ controller . abort ( signal . reason ) ;
118+ cleanup ( ) ;
119+ } ;
120+ signal . addEventListener ( 'abort' , onAbort ) ;
121+ listeners . push ( ( ) => signal . removeEventListener ( 'abort' , onAbort ) ) ;
122+ }
123+ return controller . signal ;
124+ }
125+
15126 /**
16127 * AbortSignal cannot be constructed directly.
17128 */
18129 constructor ( ) {
19130 super ( ) ;
20- throw new TypeError ( 'AbortSignal cannot be constructed directly' ) ;
131+ abortedFlags . set ( this , false ) ;
21132 }
22133
23134 /**
24135 * Returns `true` if this `AbortSignal`'s `AbortController` has signaled to abort, and `false` otherwise.
25136 */
26- // $FlowExpectedError[unsafe-getters-setters]
27137 get aborted ( ) : boolean {
28138 const aborted = abortedFlags . get ( this ) ;
29139 if ( typeof aborted !== 'boolean' ) {
@@ -36,79 +146,91 @@ export class AbortSignal extends EventTarget {
36146 }
37147 return aborted ;
38148 }
39- }
40149
41- const listeners = new WeakMap < AbortSignal , ( ( ) => void ) > ( ) ;
42- Object . defineProperty ( AbortSignal . prototype , `onabort` , {
43- enumerable : true ,
44- configurable : true ,
45- get ( ) {
46- // $FlowExpectedError[object-this-reference]
47- return listeners . get ( this ) || null ;
48- } ,
49- // $FlowExpectedError[missing-local-annot]
50- set ( value ) {
51- // $FlowExpectedError[object-this-reference]
52- const currentListener = listeners . get ( this ) ;
53- if ( currentListener === value ) return ; // same handler? do nothing!
54- if ( currentListener ) {
55- // Before setting a new listener, remove the old one if exists
56- // $FlowExpectedError[object-this-reference]
57- this . removeEventListener ( 'abort' , currentListener ) ;
58- }
59- if ( typeof value === 'function' ) {
60- // $FlowExpectedError[object-this-reference]
61- listeners . set ( this , value ) ;
62- // $FlowExpectedError[object-this-reference]
63- this . addEventListener ( 'abort' , value ) ;
64- } else {
65- // $FlowExpectedError[object-this-reference]
66- listeners . delete ( this ) ;
67- }
68- } ,
69- } ) ;
150+ get reason ( ) : unknown {
151+ return reasons . get ( this ) ;
152+ }
153+
154+ get onabort ( ) : EventCallback | null {
155+ return getEventHandlerAttribute ( this , 'abort' ) ;
156+ }
70157
158+ set onabort ( listener : ?EventCallback ) : void {
159+ setEventHandlerAttribute ( this , 'abort' , listener ) ;
160+ }
161+
162+ throwIfAborted ( ) : void {
163+ if ( this . aborted ) {
164+ throw this . reason ;
165+ }
166+ }
167+ }
71168
72169/**
73170 * Create an AbortSignal object.
74171 */
75172export function createAbortSignal ( ) : AbortSignal {
76- const signal = Object . create ( AbortSignal . prototype ) ;
77- // $FlowExpectedError[incompatible-type]
78- EventTarget . call ( signal ) ;
79- abortedFlags . set ( signal , false ) ;
80- return signal ;
173+ return new AbortSignal ( ) ;
81174}
82175
83176/**
84177 * Abort a given signal.
85178 */
86- export function abortSignal ( signal : AbortSignal ) : void {
179+ export function abortSignal (
180+ reason : unknown | void = new DOMException (
181+ 'signal is aborted without reason' ,
182+ 'AbortError' ,
183+ ) ,
184+ signal : AbortSignal ,
185+ ) : void {
87186 if ( abortedFlags . get ( signal ) !== false ) {
88187 return ;
89188 }
90189
91190 abortedFlags . set ( signal , true ) ;
92- // $FlowExpectedError[incompatible-type]
191+ reasons . set ( signal , reason ) ;
93192 signal . dispatchEvent ( new Event ( 'abort' ) ) ;
94193}
95194
96195/**
97196 * Aborted flag for each instances.
98197 */
99- const abortedFlags = new WeakMap < AbortSignal , boolean > ( )
198+ const abortedFlags = new WeakMap < AbortSignal , boolean > ( ) ;
100199
101200// Properties should be enumerable.
102201//$FlowExpectedError[cannot-write]
103202Object . defineProperties ( AbortSignal . prototype , {
104203 aborted : { enumerable : true } ,
204+ reason : { enumerable : true } ,
205+ onabort : { enumerable : true } ,
206+ throwIfAborted : { enumerable : true } ,
105207} ) ;
106208
107-
108209// `toString()` should return `"[object AbortSignal]"`
109- if ( typeof Symbol === "function" && typeof Symbol . toStringTag === "symbol" ) {
110- Object . defineProperty ( AbortSignal . prototype , Symbol . toStringTag , {
111- configurable : true ,
112- value : "AbortSignal" ,
113- } )
114- }
210+ Object . defineProperty ( AbortSignal . prototype , Symbol . toStringTag , {
211+ configurable : true ,
212+ value : 'AbortSignal' ,
213+ } ) ;
214+
215+ export const AbortSignal_public : typeof AbortSignal =
216+ /* eslint-disable no-shadow */
217+ // $FlowExpectedError[incompatible-type]
218+ function AbortSignal ( ) {
219+ throw new TypeError (
220+ "Failed to construct 'AbortSignal': Illegal constructor" ,
221+ ) ;
222+ } ;
223+
224+ // $FlowExpectedError[prop-missing]
225+ AbortSignal_public . prototype = AbortSignal . prototype ;
226+ // Copy static properties so that callers accessing them via the public constructor (e.g. `AbortSignal.timeout(0)`) still work.
227+ // $FlowFixMe[unsafe-object-assign]
228+ // $FlowFixMe[not-an-object]
229+ [ 'timeout' , 'abort' , 'any' ] . forEach ( methodName => {
230+ Object . defineProperty (
231+ AbortSignal_public ,
232+ methodName ,
233+ // $FlowExpectedError[incompatible-type]
234+ Object . getOwnPropertyDescriptor ( AbortSignal , methodName ) ,
235+ ) ;
236+ } ) ;
0 commit comments