GeezerVison is showing github.com — lib: add aborted() utility function · nodejs/node@2f79f3f
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: doc/api/util.md +45Lines changed: 45 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -1977,6 +1977,51 @@ const channel = new MessageChannel(); | 1977 | 1977 | channel.port2.postMessage(signal, [signal]); | 1978 | 1978 | ``` | 1979 | 1979 |
| | 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 | +const dependent = 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 | +const dependent = 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 | +
| 1980 | 2025 | ## `util.types` | 1981 | 2026 |
| 1982 | 2027 | <!-- YAML | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/abort_controller.js +29-5Lines changed: 29 additions & 5 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 8 | 8 | ObjectDefineProperties, | 9 | 9 | ObjectSetPrototypeOf, | 10 | 10 | ObjectDefineProperty, | | 11 | + PromiseResolve, | 11 | 12 | SafeFinalizationRegistry, | 12 | 13 | SafeSet, | 13 | 14 | Symbol, | @@ -22,11 +23,13 @@ const { | 22 | 23 | kTrustEvent, | 23 | 24 | kNewListener, | 24 | 25 | kRemoveListener, | | 26 | + kWeakHandler, | 25 | 27 | } = require('internal/event_target'); | 26 | 28 | const { | | 29 | + createDeferredPromise, | 27 | 30 | customInspectSymbol, | 28 | | - kEnumerableProperty, | 29 | 31 | kEmptyObject, | | 32 | + kEnumerableProperty, | 30 | 33 | } = require('internal/util'); | 31 | 34 | const { inspect } = require('internal/util/inspect'); | 32 | 35 | const { | @@ -38,6 +41,8 @@ const { | 38 | 41 | } = require('internal/errors'); | 39 | 42 |
| 40 | 43 | const { | | 44 | + validateAbortSignal, | | 45 | + validateObject, | 41 | 46 | validateUint32, | 42 | 47 | } = require('internal/validators'); | 43 | 48 |
| @@ -94,7 +99,7 @@ function customInspect(self, obj, depth, options) { | 94 | 99 | return `${self.constructor.name} ${inspect(obj, opts)}`; | 95 | 100 | } | 96 | 101 |
| 97 | | -function validateAbortSignal(obj) { | | 102 | +function validateThisAbortSignal(obj) { | 98 | 103 | if (obj?.[kAborted] === undefined) | 99 | 104 | throw new ERR_INVALID_THIS('AbortSignal'); | 100 | 105 | } | @@ -132,15 +137,15 @@ class AbortSignal extends EventTarget { | 132 | 137 | * @type {boolean} | 133 | 138 | */ | 134 | 139 | get aborted() { | 135 | | - validateAbortSignal(this); | | 140 | + validateThisAbortSignal(this); | 136 | 141 | return !!this[kAborted]; | 137 | 142 | } | 138 | 143 |
| 139 | 144 | /** | 140 | 145 | * @type {any} | 141 | 146 | */ | 142 | 147 | get reason() { | 143 | | - validateAbortSignal(this); | | 148 | + validateThisAbortSignal(this); | 144 | 149 | return this[kReason]; | 145 | 150 | } | 146 | 151 |
| @@ -202,7 +207,7 @@ class AbortSignal extends EventTarget { | 202 | 207 | } | 203 | 208 |
| 204 | 209 | [kTransfer]() { | 205 | | - validateAbortSignal(this); | | 210 | + validateThisAbortSignal(this); | 206 | 211 | const aborted = this.aborted; | 207 | 212 | if (aborted) { | 208 | 213 | const reason = this.reason; | @@ -369,6 +374,24 @@ function transferableAbortController() { | 369 | 374 | return AbortController[kMakeTransferable](); | 370 | 375 | } | 371 | 376 |
| | 377 | +/** | | 378 | + * @param {AbortSignal} signal | | 379 | + * @param {any} resource | | 380 | + * @returns {Promise<void>} | | 381 | + */ | | 382 | +async function aborted(signal, resource) { | | 383 | + if (signal === undefined) { | | 384 | + throw new ERR_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 | + return PromiseResolve(); | | 390 | + const abortPromise = createDeferredPromise(); | | 391 | + signal.addEventListener('abort', abortPromise.resolve, { [kWeakHandler]: resource, once: true }); | | 392 | + return abortPromise.promise; | | 393 | +} | | 394 | + | 372 | 395 | ObjectDefineProperties(AbortController.prototype, { | 373 | 396 | signal: kEnumerableProperty, | 374 | 397 | abort: kEnumerableProperty, | @@ -387,6 +410,7 @@ module.exports = { | 387 | 410 | AbortController, | 388 | 411 | AbortSignal, | 389 | 412 | ClonedAbortSignal, | | 413 | + aborted, | 390 | 414 | transferableAbortSignal, | 391 | 415 | transferableAbortController, | 392 | 416 | }; |
Collapse file Copy file name to clipboardExpand all lines: lib/util.js +3Lines changed: 3 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -400,5 +400,8 @@ module.exports = { | 400 | 400 | get transferableAbortController() { | 401 | 401 | return lazyAbortController().transferableAbortController; | 402 | 402 | }, | | 403 | + get aborted() { | | 404 | + return lazyAbortController().aborted; | | 405 | + }, | 403 | 406 | types | 404 | 407 | }; |
Collapse file Copy file name to clipboard +59Lines changed: 59 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +// Flags: --expose-gc | | 2 | +'use strict'; | | 3 | + | | 4 | +const common = require('../common'); | | 5 | +const { aborted } = require('util'); | | 6 | +const assert = require('assert'); | | 7 | +const { getEventListeners } = require('events'); | | 8 | +const { spawn } = require('child_process'); | | 9 | + | | 10 | +{ | | 11 | + // Test aborted works when provided a resource | | 12 | + const ac = new AbortController(); | | 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 | + const ac = new AbortController(); | | 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 | + const ac = new AbortController(); | | 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 | + const childProcess = 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 | +} |
|