diff --git a/lib/internal/abort_controller.js b/lib/internal/abort_controller.js index 7a4381ecd13cb3..82d86f9c59f8fa 100644 --- a/lib/internal/abort_controller.js +++ b/lib/internal/abort_controller.js @@ -13,6 +13,7 @@ const { Symbol, SymbolToStringTag, WeakRef, + PromiseResolve, } = primordials; const { @@ -22,11 +23,13 @@ const { kTrustEvent, kNewListener, kRemoveListener, + kWeakHandler, } = require('internal/event_target'); const { customInspectSymbol, kEnumerableProperty, kEmptyObject, + createDeferredPromise, } = require('internal/util'); const { inspect } = require('internal/util/inspect'); const { @@ -357,6 +360,20 @@ function transferableAbortController() { return AbortController[kMakeTransferable](); } +/** + * @param {AbortSignal} signal + * @param {any} resource + * @returns {Promise} + */ +function aborted(signal, resource = null) { + validateAbortSignal(signal, 'signal'); + 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, @@ -377,4 +394,5 @@ module.exports = { ClonedAbortSignal, transferableAbortSignal, transferableAbortController, + aborted, }; diff --git a/lib/util.js b/lib/util.js index 893a085c90aa3b..4ee13a28f58512 100644 --- a/lib/util.js +++ b/lib/util.js @@ -393,6 +393,9 @@ module.exports = { get transferableAbortController() { return lazyAbortController().transferableAbortController; }, + get aborted() { + return lazyAbortController().aborted; + }, types }; diff --git a/test/parallel/test-aborted-util.js b/test/parallel/test-aborted-util.js new file mode 100644 index 00000000000000..563d9ad922fc71 --- /dev/null +++ b/test/parallel/test-aborted-util.js @@ -0,0 +1,37 @@ +// Flags: --expose-gc +'use strict'; + +const common = require('../common'); +const { aborted } = require('util'); +const assert = require('assert'); +const { getEventListeners } = require('events'); + +{ + // Test aborted works + 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 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); + }); +}