From 883fc779b637732b18e2d0e6b1f386cebb37e93c Mon Sep 17 00:00:00 2001 From: James M Snell Date: Mon, 24 Aug 2020 13:11:23 -0700 Subject: events: allow use of AbortController with once Allows an AbortSignal to be passed in to events.once() to cancel waiting on an event. Signed-off-by: James M Snell PR-URL: https://github.com/nodejs/node/pull/34911 Reviewed-By: Denys Otrishko Reviewed-By: Benjamin Gruenbaum Reviewed-By: Matteo Collina --- lib/events.js | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) (limited to 'lib/events.js') diff --git a/lib/events.js b/lib/events.js index 48341c0b20..270588fcbc 100644 --- a/lib/events.js +++ b/lib/events.js @@ -44,6 +44,7 @@ const kRejection = SymbolFor('nodejs.rejection'); let spliceOne; const { + hideStackFrames, kEnhanceStackBeforeInspector, codes } = require('internal/errors'); @@ -57,9 +58,20 @@ const { inspect } = require('internal/util/inspect'); +const { + validateAbortSignal +} = require('internal/validators'); + const kCapture = Symbol('kCapture'); const kErrorMonitor = Symbol('events.errorMonitor'); +let DOMException; +const lazyDOMException = hideStackFrames((message, name) => { + if (DOMException === undefined) + DOMException = internalBinding('messaging').DOMException; + return new DOMException(message, name); +}); + function EventEmitter(opts) { EventEmitter.init.call(this, opts); } @@ -621,22 +633,61 @@ function unwrapListeners(arr) { return ret; } -function once(emitter, name) { +async function once(emitter, name, options = {}) { + const signal = options ? options.signal : undefined; + validateAbortSignal(signal, 'options.signal'); + if (signal && signal.aborted) + throw lazyDOMException('The operation was aborted', 'AbortError'); return new Promise((resolve, reject) => { const errorListener = (err) => { emitter.removeListener(name, resolver); + if (signal != null) { + eventTargetAgnosticRemoveListener( + signal, + 'abort', + abortListener, + { once: true }); + } reject(err); }; const resolver = (...args) => { if (typeof emitter.removeListener === 'function') { emitter.removeListener('error', errorListener); } + if (signal != null) { + eventTargetAgnosticRemoveListener( + signal, + 'abort', + abortListener, + { once: true }); + } resolve(args); }; eventTargetAgnosticAddListener(emitter, name, resolver, { once: true }); if (name !== 'error') { addErrorHandlerIfEventEmitter(emitter, errorListener, { once: true }); } + function abortListener() { + if (typeof emitter.removeListener === 'function') { + emitter.removeListener(name, resolver); + emitter.removeListener('error', errorListener); + } else { + eventTargetAgnosticRemoveListener( + emitter, + name, + resolver, + { once: true }); + eventTargetAgnosticRemoveListener( + emitter, + 'error', + errorListener, + { once: true }); + } + reject(lazyDOMException('The operation was aborted', 'AbortError')); + } + if (signal != null) { + signal.addEventListener('abort', abortListener, { once: true }); + } }); } -- cgit v1.2.1