Skip to content

Commit 2f79f3f

Browse files
debadree25danielleadams
authored andcommitted
lib: add aborted() utility function
Fixes: #37220 Refs: #36607 PR-URL: #46494 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: Antoine du Hamel <[email protected]>
1 parent 4fb2fc7 commit 2f79f3f

File tree

4 files changed

+136
-5
lines changed

4 files changed

+136
-5
lines changed

‎doc/api/util.md‎

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1977,6 +1977,51 @@ const channel = new MessageChannel();
19771977
channel.port2.postMessage(signal, [signal]);
19781978
```
19791979
1980+
## `util.aborted(signal, resource)`
1981+
1982+
<!-- YAML
1983+
added: REPLACEME
1984+
-->
1985+
1986+
> Stability: 1 - Experimental
1987+
1988+
* `signal`{AbortSignal}
1989+
* `resource`{Object} Any non-null entity, reference to which is held weakly.
1990+
* Returns:{Promise}
1991+
1992+
Listens to abort event on the provided `signal` and
1993+
returns a promise that is fulfilled when the `signal` is
1994+
aborted. If the passed `resource` is garbage collected before the `signal` is
1995+
aborted, the returned promise shall remain pending indefinitely.
1996+
1997+
```cjs
1998+
const{aborted } =require('node:util');
1999+
2000+
constdependent=obtainSomethingAbortable();
2001+
2002+
aborted(dependent.signal, dependent).then(() =>{
2003+
// Do something when dependent is aborted.
2004+
});
2005+
2006+
dependent.on('event', () =>{
2007+
dependent.abort();
2008+
});
2009+
```
2010+
2011+
```mjs
2012+
import{aborted } from'node:util';
2013+
2014+
constdependent=obtainSomethingAbortable();
2015+
2016+
aborted(dependent.signal, dependent).then(() =>{
2017+
// Do something when dependent is aborted.
2018+
});
2019+
2020+
dependent.on('event', () =>{
2021+
dependent.abort();
2022+
});
2023+
```
2024+
19802025
## `util.types`
19812026
19822027
<!-- YAML

‎lib/internal/abort_controller.js‎

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const{
88
ObjectDefineProperties,
99
ObjectSetPrototypeOf,
1010
ObjectDefineProperty,
11+
PromiseResolve,
1112
SafeFinalizationRegistry,
1213
SafeSet,
1314
Symbol,
@@ -22,11 +23,13 @@ const{
2223
kTrustEvent,
2324
kNewListener,
2425
kRemoveListener,
26+
kWeakHandler,
2527
}=require('internal/event_target');
2628
const{
29+
createDeferredPromise,
2730
customInspectSymbol,
28-
kEnumerableProperty,
2931
kEmptyObject,
32+
kEnumerableProperty,
3033
}=require('internal/util');
3134
const{ inspect }=require('internal/util/inspect');
3235
const{
@@ -38,6 +41,8 @@ const{
3841
}=require('internal/errors');
3942

4043
const{
44+
validateAbortSignal,
45+
validateObject,
4146
validateUint32,
4247
}=require('internal/validators');
4348

@@ -94,7 +99,7 @@ function customInspect(self, obj, depth, options){
9499
return`${self.constructor.name}${inspect(obj,opts)}`;
95100
}
96101

97-
functionvalidateAbortSignal(obj){
102+
functionvalidateThisAbortSignal(obj){
98103
if(obj?.[kAborted]===undefined)
99104
thrownewERR_INVALID_THIS('AbortSignal');
100105
}
@@ -132,15 +137,15 @@ class AbortSignal extends EventTarget{
132137
* @type{boolean}
133138
*/
134139
getaborted(){
135-
validateAbortSignal(this);
140+
validateThisAbortSignal(this);
136141
return!!this[kAborted];
137142
}
138143

139144
/**
140145
* @type{any}
141146
*/
142147
getreason(){
143-
validateAbortSignal(this);
148+
validateThisAbortSignal(this);
144149
returnthis[kReason];
145150
}
146151

@@ -202,7 +207,7 @@ class AbortSignal extends EventTarget{
202207
}
203208

204209
[kTransfer](){
205-
validateAbortSignal(this);
210+
validateThisAbortSignal(this);
206211
constaborted=this.aborted;
207212
if(aborted){
208213
constreason=this.reason;
@@ -369,6 +374,24 @@ function transferableAbortController(){
369374
returnAbortController[kMakeTransferable]();
370375
}
371376

377+
/**
378+
* @param{AbortSignal} signal
379+
* @param{any} resource
380+
* @returns{Promise<void>}
381+
*/
382+
asyncfunctionaborted(signal,resource){
383+
if(signal===undefined){
384+
thrownewERR_INVALID_ARG_TYPE('signal','AbortSignal',signal);
385+
}
386+
validateAbortSignal(signal,'signal');
387+
validateObject(resource,'resource',{nullable: false,allowFunction: true,allowArray: true});
388+
if(signal.aborted)
389+
returnPromiseResolve();
390+
constabortPromise=createDeferredPromise();
391+
signal.addEventListener('abort',abortPromise.resolve,{[kWeakHandler]: resource,once: true});
392+
returnabortPromise.promise;
393+
}
394+
372395
ObjectDefineProperties(AbortController.prototype,{
373396
signal: kEnumerableProperty,
374397
abort: kEnumerableProperty,
@@ -387,6 +410,7 @@ module.exports ={
387410
AbortController,
388411
AbortSignal,
389412
ClonedAbortSignal,
413+
aborted,
390414
transferableAbortSignal,
391415
transferableAbortController,
392416
};

‎lib/util.js‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,5 +400,8 @@ module.exports ={
400400
gettransferableAbortController(){
401401
returnlazyAbortController().transferableAbortController;
402402
},
403+
getaborted(){
404+
returnlazyAbortController().aborted;
405+
},
403406
types
404407
};

‎test/parallel/test-aborted-util.js‎

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Flags: --expose-gc
2+
'use strict';
3+
4+
constcommon=require('../common');
5+
const{ aborted }=require('util');
6+
constassert=require('assert');
7+
const{ getEventListeners }=require('events');
8+
const{ spawn }=require('child_process');
9+
10+
{
11+
// Test aborted works when provided a resource
12+
constac=newAbortController();
13+
aborted(ac.signal,{}).then(common.mustCall());
14+
ac.abort();
15+
assert.strictEqual(ac.signal.aborted,true);
16+
assert.strictEqual(getEventListeners(ac.signal,'abort').length,0);
17+
}
18+
19+
{
20+
// Test aborted with gc cleanup
21+
constac=newAbortController();
22+
aborted(ac.signal,{}).then(common.mustNotCall());
23+
setImmediate(()=>{
24+
global.gc();
25+
ac.abort();
26+
assert.strictEqual(ac.signal.aborted,true);
27+
assert.strictEqual(getEventListeners(ac.signal,'abort').length,0);
28+
});
29+
}
30+
31+
{
32+
// Fails with error if not provided abort signal
33+
Promise.all([{},null,undefined,Symbol(),[],1,0,1n,true,false,'a',()=>{}].map((sig)=>
34+
assert.rejects(aborted(sig,{}),{
35+
code: 'ERR_INVALID_ARG_TYPE',
36+
})
37+
)).then(common.mustCall());
38+
}
39+
40+
{
41+
// Fails if not provided a resource
42+
constac=newAbortController();
43+
Promise.all([null,undefined,0,1,0n,1n,Symbol(),'','a'].map((resource)=>
44+
assert.rejects(aborted(ac.signal,resource),{
45+
code: 'ERR_INVALID_ARG_TYPE',
46+
})
47+
)).then(common.mustCall());
48+
}
49+
50+
{
51+
constchildProcess=spawn(process.execPath,['--input-type=module']);
52+
childProcess.on('exit',common.mustCall((code)=>{
53+
assert.strictEqual(code,13);
54+
}));
55+
childProcess.stdin.end(`
56+
import{aborted } from 'node:util'
57+
await aborted(new AbortController().signal,{});
58+
`);
59+
}

0 commit comments

Comments
(0)