mirror of
https://github.com/nodejs/node.git
synced 2026-10-14 10:26:26 -04:00
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:
1 parent
4d36ebddda
commit
6d584ae413
4 files changed
+136
-5
No files matched your search
@@ -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
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
@@ -393,6 +393,9 @@ module.exports = {
|
||||
get transferableAbortController() {
|
||||
return lazyAbortController().transferableAbortController;
|
||||
},
|
||||
get aborted() {
|
||||
return lazyAbortController().aborted;
|
||||
},
|
||||
types
|
||||
};
|
||||
|
||||
|
||||
@@ -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, {});
|
||||
`);
|
||||
}
|
||||
Reference in new issue
Block a user