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 {
1550 /**
16- * AbortSignal cannot be constructed directly.
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
1755 */
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+
18126 constructor ( ) {
19127 super ( ) ;
20- throw new TypeError ( 'AbortSignal cannot be constructed directly' ) ;
128+ abortedFlags . set ( this , false ) ;
21129 }
22130
23131 /**
24132 * Returns `true` if this `AbortSignal`'s `AbortController` has signaled to abort, and `false` otherwise.
25133 */
26- // $FlowExpectedError[unsafe-getters-setters]
27134 get aborted ( ) : boolean {
28135 const aborted = abortedFlags . get ( this ) ;
29136 if ( typeof aborted !== 'boolean' ) {
@@ -36,79 +143,93 @@ export class AbortSignal extends EventTarget {
36143 }
37144 return aborted ;
38145 }
39- }
40146
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- } ) ;
147+ get reason ( ) : unknown {
148+ return reasons . get ( this ) ;
149+ }
150+
151+ get onabort ( ) : EventCallback | null {
152+ return getEventHandlerAttribute ( this , 'abort' ) ;
153+ }
154+
155+ set onabort ( listener : ?EventCallback ) : void {
156+ setEventHandlerAttribute ( this , 'abort' , listener ) ;
157+ }
70158
159+ throwIfAborted ( ) : void {
160+ if ( this . aborted ) {
161+ throw this . reason ;
162+ }
163+ }
164+ }
71165
72166/**
73167 * Create an AbortSignal object.
74168 */
75169export 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 ;
170+ return new AbortSignal ( ) ;
81171}
82172
83173/**
84174 * Abort a given signal.
85175 */
86- export function abortSignal ( signal : AbortSignal ) : void {
176+ export function abortSignal (
177+ reason : unknown | void = new DOMException (
178+ 'signal is aborted without reason' ,
179+ 'AbortError' ,
180+ ) ,
181+ signal : AbortSignal ,
182+ ) : void {
87183 if ( abortedFlags . get ( signal ) !== false ) {
88184 return ;
89185 }
90186
91187 abortedFlags . set ( signal , true ) ;
92- // $FlowExpectedError[incompatible-type]
188+ reasons . set ( signal , reason ) ;
93189 signal . dispatchEvent ( new Event ( 'abort' ) ) ;
94190}
95191
96192/**
97193 * Aborted flag for each instances.
98194 */
99- const abortedFlags = new WeakMap < AbortSignal , boolean > ( )
195+ const abortedFlags = new WeakMap < AbortSignal , boolean > ( ) ;
100196
101197// Properties should be enumerable.
102198//$FlowExpectedError[cannot-write]
103199Object . defineProperties ( AbortSignal . prototype , {
104200 aborted : { enumerable : true } ,
201+ reason : { enumerable : true } ,
202+ onabort : { enumerable : true } ,
203+ throwIfAborted : { enumerable : true } ,
105204} ) ;
106205
107-
108206// `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- }
207+ Object . defineProperty ( AbortSignal . prototype , Symbol . toStringTag , {
208+ configurable : true ,
209+ value : 'AbortSignal' ,
210+ } ) ;
211+
212+ /**
213+ * AbortSignal cannot be constructed directly.
214+ * So this wrapper is used to achieve such behavior
215+ */
216+ export const AbortSignal_public : typeof AbortSignal =
217+ /* eslint-disable no-shadow */
218+ // $FlowExpectedError[incompatible-type]
219+ function AbortSignal ( ) {
220+ throw new TypeError (
221+ "Failed to construct 'AbortSignal': Illegal constructor" ,
222+ ) ;
223+ } ;
224+
225+ // 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.
226+ // $FlowFixMe[unsafe-object-assign]
227+ // $FlowFixMe[not-an-object]
228+ Object . getOwnPropertyNames ( AbortSignal ) . forEach ( methodName => {
229+ Object . defineProperty (
230+ AbortSignal_public ,
231+ methodName ,
232+ // $FlowExpectedError[incompatible-type]
233+ Object . getOwnPropertyDescriptor ( AbortSignal , methodName ) ,
234+ ) ;
235+ } ) ;
0 commit comments