lib: add aborted() utility function

Fixes: https://github.com/nodejs/node/issues/37220
Refs: https://github.com/nodejs/node/pull/36607
PR-URL: https://github.com/nodejs/node/pull/46494
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
This commit is contained in:
Debadree Chatterjee authored and GitHub committed 2023-02-07 17:43:57 +00:00
1 parent 4d36ebddda
commit 6d584ae413
4 files changed
+136 -5

No files matched your search

+45
View File
@@ -1989,6 +1989,51 @@ const channel = new MessageChannel();
channel.port2.postMessage(signal, [signal]);
```
## `util.aborted(signal, resource)`
<!-- YAML
added: REPLACEME
-->
> Stability: 1 - Experimental
* `signal` {AbortSignal}
* `resource` {Object} Any non-null entity, reference to which is held weakly.
* Returns: {Promise}
Listens to abort event on the provided `signal` and
returns a promise that is fulfilled when the `signal` is
aborted. If the passed `resource` is garbage collected before the `signal` is
aborted, the returned promise shall remain pending indefinitely.
```cjs
const { aborted } = require('node:util');
const dependent = obtainSomethingAbortable();
aborted(dependent.signal, dependent).then(() => {
// Do something when dependent is aborted.
});
dependent.on('event', () => {
dependent.abort();
});
```
```mjs
import { aborted } from 'node:util';
const dependent = obtainSomethingAbortable();
aborted(dependent.signal, dependent).then(() => {
// Do something when dependent is aborted.
});
dependent.on('event', () => {
dependent.abort();
});
```
## `util.types`
<!-- YAML
+29 -5
View File
@@ -8,6 +8,7 @@ const {
ObjectDefineProperties,
ObjectSetPrototypeOf,
ObjectDefineProperty,
PromiseResolve,
SafeFinalizationRegistry,
SafeSet,
Symbol,
@@ -22,11 +23,13 @@ const {
kTrustEvent,
kNewListener,
kRemoveListener,
kWeakHandler,
} = require('internal/event_target');
const {
createDeferredPromise,
customInspectSymbol,
kEnumerableProperty,
kEmptyObject,
kEnumerableProperty,
} = require('internal/util');
const { inspect } = require('internal/util/inspect');
const {
@@ -38,6 +41,8 @@ const {
} = require('internal/errors');
const {
validateAbortSignal,
validateObject,
validateUint32,
} = require('internal/validators');
@@ -94,7 +99,7 @@ function customInspect(self, obj, depth, options) {
return `${self.constructor.name} ${inspect(obj, opts)}`;
}
function validateAbortSignal(obj) {
function validateThisAbortSignal(obj) {
if (obj?.[kAborted] === undefined)
throw new ERR_INVALID_THIS('AbortSignal');
}
@@ -132,7 +137,7 @@ class AbortSignal extends EventTarget {
* @type {boolean}
*/
get aborted() {
validateAbortSignal(this);
validateThisAbortSignal(this);
return !!this[kAborted];
}
@@ -140,7 +145,7 @@ class AbortSignal extends EventTarget {
* @type {any}
*/
get reason() {
validateAbortSignal(this);
validateThisAbortSignal(this);
return this[kReason];
}
@@ -202,7 +207,7 @@ class AbortSignal extends EventTarget {
}
[kTransfer]() {
validateAbortSignal(this);
validateThisAbortSignal(this);
const aborted = this.aborted;
if (aborted) {
const reason = this.reason;
@@ -357,6 +362,24 @@ function transferableAbortController() {
return AbortController[kMakeTransferable]();
}
/**
* @param {AbortSignal} signal
* @param {any} resource
* @returns {Promise<void>}
*/
async function aborted(signal, resource) {
if (signal === undefined) {
throw new ERR_INVALID_ARG_TYPE('signal', 'AbortSignal', signal);
}
validateAbortSignal(signal, 'signal');
validateObject(resource, 'resource', { nullable: false, allowFunction: true, allowArray: true });
if (signal.aborted)
return PromiseResolve();
const abortPromise = createDeferredPromise();
signal.addEventListener('abort', abortPromise.resolve, { [kWeakHandler]: resource, once: true });
return abortPromise.promise;
}
ObjectDefineProperties(AbortController.prototype, {
signal: kEnumerableProperty,
abort: kEnumerableProperty,
@@ -375,6 +398,7 @@ module.exports = {
AbortController,
AbortSignal,
ClonedAbortSignal,
aborted,
transferableAbortSignal,
transferableAbortController,
};
+3
View File
@@ -393,6 +393,9 @@ module.exports = {
get transferableAbortController() {
return lazyAbortController().transferableAbortController;
},
get aborted() {
return lazyAbortController().aborted;
},
types
};
+59
View File
@@ -0,0 +1,59 @@
// Flags: --expose-gc
'use strict';
const common = require('../common');
const { aborted } = require('util');
const assert = require('assert');
const { getEventListeners } = require('events');
const { spawn } = require('child_process');
{
// Test aborted works when provided a resource
const ac = new AbortController();
aborted(ac.signal, {}).then(common.mustCall());
ac.abort();
assert.strictEqual(ac.signal.aborted, true);
assert.strictEqual(getEventListeners(ac.signal, 'abort').length, 0);
}
{
// Test aborted with gc cleanup
const ac = new AbortController();
aborted(ac.signal, {}).then(common.mustNotCall());
setImmediate(() => {
global.gc();
ac.abort();
assert.strictEqual(ac.signal.aborted, true);
assert.strictEqual(getEventListeners(ac.signal, 'abort').length, 0);
});
}
{
// Fails with error if not provided abort signal
Promise.all([{}, null, undefined, Symbol(), [], 1, 0, 1n, true, false, 'a', () => {}].map((sig) =>
assert.rejects(aborted(sig, {}), {
code: 'ERR_INVALID_ARG_TYPE',
})
)).then(common.mustCall());
}
{
// Fails if not provided a resource
const ac = new AbortController();
Promise.all([null, undefined, 0, 1, 0n, 1n, Symbol(), '', 'a'].map((resource) =>
assert.rejects(aborted(ac.signal, resource), {
code: 'ERR_INVALID_ARG_TYPE',
})
)).then(common.mustCall());
}
{
const childProcess = spawn(process.execPath, ['--input-type=module']);
childProcess.on('exit', common.mustCall((code) => {
assert.strictEqual(code, 13);
}));
childProcess.stdin.end(`
import { aborted } from 'node:util';
await aborted(new AbortController().signal, {});
`);
}