1
1
import { TaskAbortError } from './exceptions'
2
2
import type { AbortSignalWithReason , TaskResult } from './types'
3
- import { addAbortSignalListener , catchRejection } from './utils'
3
+ import { addAbortSignalListener , catchRejection , noop } from './utils'
4
4
5
5
/**
6
6
* Synchronously raises {@link TaskAbortError} if the task tied to the input `signal` has been cancelled.
@@ -15,24 +15,29 @@ export const validateActive = (signal: AbortSignal): void => {
15
15
}
16
16
17
17
/**
18
- * Returns a promise that will reject { @link TaskAbortError} if the task is cancelled.
19
- * @param signal
20
- * @returns
18
+ * Generates a race between the promise(s) and the AbortSignal
19
+ * This avoids `Promise.race()`-related memory leaks:
20
+ * https://github.com/nodejs/node/issues/17469#issuecomment-349794909
21
21
*/
22
- export const promisifyAbortSignal = (
23
- signal : AbortSignalWithReason < string >
24
- ) : Promise < never > => {
25
- return catchRejection (
26
- new Promise < never > ( ( _ , reject ) => {
27
- const notifyRejection = ( ) => reject ( new TaskAbortError ( signal . reason ) )
22
+ export function raceWithSignal < T > (
23
+ signal : AbortSignalWithReason < string > ,
24
+ promise : Promise < T >
25
+ ) : Promise < T > {
26
+ let cleanup = noop
27
+ return new Promise < T > ( ( resolve , reject ) => {
28
+ const notifyRejection = ( ) => reject ( new TaskAbortError ( signal . reason ) )
29
+
30
+ if ( signal . aborted ) {
31
+ notifyRejection ( )
32
+ return
33
+ }
28
34
29
- if ( signal . aborted ) {
30
- notifyRejection ( )
31
- } else {
32
- addAbortSignalListener ( signal , notifyRejection )
33
- }
34
- } )
35
- )
35
+ cleanup = addAbortSignalListener ( signal , notifyRejection )
36
+ promise . finally ( ( ) => cleanup ( ) ) . then ( resolve , reject )
37
+ } ) . finally ( ( ) => {
38
+ // after this point, replace `cleanup` with a noop, so there is no reference to `signal` any more
39
+ cleanup = noop
40
+ } )
36
41
}
37
42
38
43
/**
@@ -73,7 +78,7 @@ export const runTask = async <T>(
73
78
export const createPause = < T > ( signal : AbortSignal ) => {
74
79
return ( promise : Promise < T > ) : Promise < T > => {
75
80
return catchRejection (
76
- Promise . race ( [ promisifyAbortSignal ( signal ) , promise ] ) . then ( ( output ) => {
81
+ raceWithSignal ( signal , promise ) . then ( ( output ) => {
77
82
validateActive ( signal )
78
83
return output
79
84
} )
0 commit comments