Files
node/lib/events.js
Matteo Collina 35115c05ad events: avoid retaining removed event names
Restore shape mode for preallocated EventEmitters while deleting dynamic
names from ordinary EventEmitters. Keeping every removed name as an
undefined property causes unbounded memory growth when event names are
unstructured.

Refs: https://github.com/nodejs/node/pull/64373
Signed-off-by: Matteo Collina <hello@matteocollina.com>
PR-URL: https://github.com/nodejs/node/pull/64475
Reviewed-By: Robert Nagy <ronagy@icloud.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
2026-07-13 23:32:39 +00:00

1257 lines
35 KiB
JavaScript

// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const {
ArrayPrototypeJoin,
ArrayPrototypePop,
ArrayPrototypePush,
ArrayPrototypeSlice,
ArrayPrototypeSplice,
ArrayPrototypeUnshift,
AsyncIteratorPrototype,
Boolean,
Error,
ErrorCaptureStackTrace,
FunctionPrototypeBind,
NumberMAX_SAFE_INTEGER,
ObjectDefineProperties,
ObjectDefineProperty,
ObjectGetPrototypeOf,
ObjectSetPrototypeOf,
Promise,
PromiseReject,
PromiseResolve,
ReflectApply,
ReflectOwnKeys,
String,
StringPrototypeSplit,
Symbol,
SymbolAsyncIterator,
SymbolDispose,
SymbolFor,
} = primordials;
const kRejection = SymbolFor('nodejs.rejection');
const { kEmptyObject, spliceOne } = require('internal/util');
const {
inspect,
identicalSequenceRange,
} = require('internal/util/inspect');
let FixedQueue;
let kFirstEventParam;
let kResistStopPropagation;
const {
AbortError,
codes: {
ERR_INVALID_ARG_TYPE,
ERR_UNHANDLED_ERROR,
},
genericNodeError,
kEnhanceStackBeforeInspector,
} = require('internal/errors');
const {
validateInteger,
validateAbortSignal,
validateBoolean,
validateFunction,
validateNumber,
validateObject,
validateString,
} = require('internal/validators');
const { addAbortListener } = require('internal/events/abort_listener');
const kCapture = Symbol('kCapture');
const kErrorMonitor = Symbol('events.errorMonitor');
const kShapeMode = Symbol('shapeMode');
const kEmitting = Symbol('events.emitting');
const kMaxEventTargetListeners = Symbol('events.maxEventTargetListeners');
const kMaxEventTargetListenersWarned =
Symbol('events.maxEventTargetListenersWarned');
const kWatermarkData = SymbolFor('nodejs.watermarkData');
let EventEmitterAsyncResource;
// The EventEmitterAsyncResource has to be initialized lazily because event.js
// is loaded so early in the bootstrap process, before async_hooks is available.
//
// This implementation was adapted straight from addaleax's
// eventemitter-asyncresource MIT-licensed userland module.
// https://github.com/addaleax/eventemitter-asyncresource
function lazyEventEmitterAsyncResource() {
if (EventEmitterAsyncResource === undefined) {
const {
AsyncResource,
} = require('async_hooks');
class EventEmitterReferencingAsyncResource extends AsyncResource {
#eventEmitter;
/**
* @param {EventEmitter} ee
* @param {string} [type]
* @param {{
* triggerAsyncId?: number,
* requireManualDestroy?: boolean,
* }} [options]
*/
constructor(ee, type, options) {
super(type, options);
this.#eventEmitter = ee;
}
/**
* @type {EventEmitter}
*/
get eventEmitter() {
return this.#eventEmitter;
}
}
EventEmitterAsyncResource =
class EventEmitterAsyncResource extends EventEmitter {
#asyncResource;
/**
* @param {{
* name?: string,
* triggerAsyncId?: number,
* requireManualDestroy?: boolean,
* }} [options]
*/
constructor(options = undefined) {
let name;
if (typeof options === 'string') {
name = options;
options = undefined;
} else {
if (new.target === EventEmitterAsyncResource) {
validateString(options?.name, 'options.name');
}
name = options?.name || new.target.name;
}
super(options);
this.#asyncResource = new EventEmitterReferencingAsyncResource(this, name, options);
}
/**
* @param {symbol|string} event
* @param {any[]} args
* @returns {boolean}
*/
emit(event, ...args) {
const asyncResource = this.#asyncResource;
ArrayPrototypeUnshift(args, super.emit, this, event);
return ReflectApply(asyncResource.runInAsyncScope, asyncResource,
args);
}
/**
* @returns {void}
*/
emitDestroy() {
this.#asyncResource.emitDestroy();
}
/**
* @type {number}
*/
get asyncId() {
return this.#asyncResource.asyncId();
}
/**
* @type {number}
*/
get triggerAsyncId() {
return this.#asyncResource.triggerAsyncId();
}
/**
* @type {EventEmitterReferencingAsyncResource}
*/
get asyncResource() {
return this.#asyncResource;
}
};
}
return EventEmitterAsyncResource;
}
/**
* Creates a new `EventEmitter` instance.
* @param {{ captureRejections?: boolean; }} [opts]
* @constructs EventEmitter
*/
function EventEmitter(opts) {
EventEmitter.init.call(this, opts);
}
module.exports = EventEmitter;
module.exports.addAbortListener = addAbortListener;
module.exports.once = once;
module.exports.on = on;
module.exports.getEventListeners = getEventListeners;
module.exports.getMaxListeners = getMaxListeners;
module.exports.listenerCount = listenerCount;
// Backwards-compat with node 0.10.x
EventEmitter.EventEmitter = EventEmitter;
EventEmitter.usingDomains = false;
EventEmitter.captureRejectionSymbol = kRejection;
ObjectDefineProperty(EventEmitter, 'captureRejections', {
__proto__: null,
get() {
return EventEmitter.prototype[kCapture];
},
set(value) {
validateBoolean(value, 'EventEmitter.captureRejections');
EventEmitter.prototype[kCapture] = value;
},
enumerable: true,
});
ObjectDefineProperty(EventEmitter, 'EventEmitterAsyncResource', {
__proto__: null,
enumerable: true,
get: lazyEventEmitterAsyncResource,
set: undefined,
configurable: true,
});
EventEmitter.errorMonitor = kErrorMonitor;
// The default for captureRejections is false
ObjectDefineProperty(EventEmitter.prototype, kCapture, {
__proto__: null,
value: false,
writable: true,
enumerable: false,
});
EventEmitter.prototype._events = undefined;
EventEmitter.prototype._eventsCount = 0;
EventEmitter.prototype._maxListeners = undefined;
// By default EventEmitters will print a warning if more than 10 listeners are
// added to it. This is a useful default which helps finding memory leaks.
let defaultMaxListeners = 10;
let isEventTarget;
function checkListener(listener) {
validateFunction(listener, 'listener');
}
ObjectDefineProperty(EventEmitter, 'defaultMaxListeners', {
__proto__: null,
enumerable: true,
get: function() {
return defaultMaxListeners;
},
set: function(arg) {
validateNumber(arg, 'defaultMaxListeners', 0);
defaultMaxListeners = arg;
},
});
ObjectDefineProperties(EventEmitter, {
kMaxEventTargetListeners: {
__proto__: null,
value: kMaxEventTargetListeners,
enumerable: false,
configurable: false,
writable: false,
},
kMaxEventTargetListenersWarned: {
__proto__: null,
value: kMaxEventTargetListenersWarned,
enumerable: false,
configurable: false,
writable: false,
},
});
/**
* Sets the max listeners.
* @param {number} n
* @param {EventTarget[] | EventEmitter[]} [eventTargets]
* @returns {void}
*/
EventEmitter.setMaxListeners =
function(n = defaultMaxListeners, ...eventTargets) {
validateNumber(n, 'setMaxListeners', 0);
if (eventTargets.length === 0) {
defaultMaxListeners = n;
} else {
if (isEventTarget === undefined)
isEventTarget = require('internal/event_target').isEventTarget;
for (let i = 0; i < eventTargets.length; i++) {
const target = eventTargets[i];
if (isEventTarget(target)) {
target[kMaxEventTargetListeners] = n;
target[kMaxEventTargetListenersWarned] = false;
} else if (typeof target.setMaxListeners === 'function') {
target.setMaxListeners(n);
} else {
throw new ERR_INVALID_ARG_TYPE(
'eventTargets',
['EventEmitter', 'EventTarget'],
target);
}
}
}
};
// If you're updating this function definition, please also update any
// re-definitions, such as the one in the Domain module (lib/domain.js).
EventEmitter.init = function(opts) {
if (this._events === undefined ||
this._events === ObjectGetPrototypeOf(this)._events) {
this._events = { __proto__: null };
this._eventsCount = 0;
this[kShapeMode] = false;
} else {
this[kShapeMode] = true;
}
this._maxListeners ||= undefined;
if (opts?.captureRejections) {
validateBoolean(opts.captureRejections, 'options.captureRejections');
this[kCapture] = Boolean(opts.captureRejections);
} else {
// Assigning the kCapture property directly saves an expensive
// prototype lookup in a very sensitive hot path.
this[kCapture] = EventEmitter.prototype[kCapture];
}
};
function addCatch(that, promise, type, args) {
if (!that[kCapture]) {
return;
}
// Handle Promises/A+ spec, then could be a getter
// that throws on second use.
try {
const then = promise.then;
if (typeof then === 'function') {
then.call(promise, undefined, function(err) {
// The callback is called with nextTick to avoid a follow-up
// rejection from this promise.
process.nextTick(emitUnhandledRejectionOrErr, that, err, type, args);
});
}
} catch (err) {
that.emit('error', err);
}
}
function emitUnhandledRejectionOrErr(ee, err, type, args) {
if (typeof ee[kRejection] === 'function') {
ee[kRejection](err, type, ...args);
} else {
// We have to disable the capture rejections mechanism, otherwise
// we might end up in an infinite loop.
const prev = ee[kCapture];
// If the error handler throws, it is not catchable and it
// will end up in 'uncaughtException'. We restore the previous
// value of kCapture in case the uncaughtException is present
// and the exception is handled.
try {
ee[kCapture] = false;
ee.emit('error', err);
} finally {
ee[kCapture] = prev;
}
}
}
/**
* Increases the max listeners of the event emitter.
* @param {number} n
* @returns {EventEmitter}
*/
EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) {
validateNumber(n, 'setMaxListeners', 0);
this._maxListeners = n;
return this;
};
function _getMaxListeners(that) {
if (that._maxListeners === undefined)
return EventEmitter.defaultMaxListeners;
return that._maxListeners;
}
/**
* Returns the current max listener value for the event emitter.
* @returns {number}
*/
EventEmitter.prototype.getMaxListeners = function getMaxListeners() {
return _getMaxListeners(this);
};
function enhanceStackTrace(err, own) {
let ctorInfo = '';
try {
const { name } = this.constructor;
if (name !== 'EventEmitter')
ctorInfo = ` on ${name} instance`;
} catch {
// Continue regardless of error.
}
const sep = `\nEmitted 'error' event${ctorInfo} at:\n`;
const errStack = ArrayPrototypeSlice(
StringPrototypeSplit(err.stack, '\n'), 1);
const ownStack = ArrayPrototypeSlice(
StringPrototypeSplit(own.stack, '\n'), 1);
const { len, offset } = identicalSequenceRange(ownStack, errStack);
if (len > 0) {
ArrayPrototypeSplice(ownStack, offset + 1, len - 2,
' [... lines matching original stack trace ...]');
}
return err.stack + sep + ArrayPrototypeJoin(ownStack, '\n');
}
function getUnhandledErrorException(ee, args) {
let er;
if (args.length > 0)
er = args[0];
if (er instanceof Error) {
try {
const capture = {};
ErrorCaptureStackTrace(capture, EventEmitter.prototype.emit);
ObjectDefineProperty(er, kEnhanceStackBeforeInspector, {
__proto__: null,
value: FunctionPrototypeBind(enhanceStackTrace, ee, er, capture),
configurable: true,
});
} catch {
// Continue regardless of error.
}
return er;
}
let stringifiedEr;
try {
stringifiedEr = inspect(er);
} catch {
stringifiedEr = er;
}
// At least give some kind of context to the user
const err = new ERR_UNHANDLED_ERROR(stringifiedEr);
err.context = er;
return err;
}
/**
* Synchronously calls each of the listeners registered
* for the event.
* @param {string | symbol} type
* @param {...any} [args]
* @returns {boolean}
*/
EventEmitter.prototype.emit = function emit(type, ...args) {
let doError = (type === 'error');
const events = this._events;
if (events !== undefined) {
if (doError && events[kErrorMonitor] !== undefined)
this.emit(kErrorMonitor, ...args);
doError &&= events.error === undefined;
} else if (!doError)
return false;
// If there is no 'error' event listener then throw.
if (doError) {
const er = getUnhandledErrorException(this, args);
// Note: The comments on the `throw` lines are intentional, they show
// up in Node's output if this results in an unhandled exception.
throw er; // Unhandled 'error' event
}
const handler = events[type];
if (handler === undefined)
return false;
if (typeof handler === 'function') {
const result = ReflectApply(handler, this, args);
// We check if result is undefined first because that
// is the most common case so we do not pay any perf
// penalty
if (result !== undefined && result !== null) {
addCatch(this, result, type, args);
}
} else {
handler[kEmitting]++;
try {
for (let i = 0; i < handler.length; ++i) {
const result = ReflectApply(handler[i], this, args);
// We check if result is undefined first because that
// is the most common case so we do not pay any perf
// penalty.
// This code is duplicated because extracting it away
// would make it non-inlineable.
if (result !== undefined && result !== null) {
addCatch(this, result, type, args);
}
}
} finally {
handler[kEmitting]--;
}
}
return true;
};
function _addListener(target, type, listener, prepend) {
let m;
let events;
let existing;
checkListener(listener);
events = target._events;
if (events === undefined) {
events = target._events = { __proto__: null };
target._eventsCount = 0;
} else {
// To avoid recursion in the case that type === "newListener"! Before
// adding it to the listeners, first emit "newListener".
if (events.newListener !== undefined) {
target.emit('newListener', type,
listener.listener ?? listener);
// Re-assign `events` because a newListener handler could have caused the
// this._events to be assigned to a new object
events = target._events;
}
existing = events[type];
}
if (existing === undefined) {
// Optimize the case of one listener. Don't need the extra array object.
events[type] = listener;
++target._eventsCount;
} else {
if (typeof existing === 'function') {
// Adding the second element, need to change to array.
existing = prepend ? [listener, existing] : [existing, listener];
existing[kEmitting] = 0;
events[type] = existing;
// If we've already got an array, just append.
} else {
existing = ensureMutableListenerArray(events, type, existing);
if (prepend) {
existing.unshift(listener);
} else {
existing.push(listener);
}
}
// Check for listener leak
m = _getMaxListeners(target);
if (m > 0 && existing.length > m && !existing.warned)
warnMaxListenersExceeded(target, type, existing, m);
}
return target;
}
function warnMaxListenersExceeded(target, type, existing, m) {
existing.warned = true;
// No error code for this since it is a Warning
const w = genericNodeError(
`Possible EventEmitter memory leak detected. ${existing.length} ${String(type)} listeners ` +
`added to ${inspect(target, { depth: -1 })}. MaxListeners is ${m}. Use emitter.setMaxListeners() to increase limit`,
{ name: 'MaxListenersExceededWarning', emitter: target, type: type, count: existing.length });
process.emitWarning(w);
}
/**
* Adds a listener to the event emitter.
* @param {string | symbol} type
* @param {Function} listener
* @returns {EventEmitter}
*/
EventEmitter.prototype.addListener = function addListener(type, listener) {
return _addListener(this, type, listener, false);
};
EventEmitter.prototype.on = EventEmitter.prototype.addListener;
/**
* Adds the `listener` function to the beginning of
* the listeners array.
* @param {string | symbol} type
* @param {Function} listener
* @returns {EventEmitter}
*/
EventEmitter.prototype.prependListener =
function prependListener(type, listener) {
return _addListener(this, type, listener, true);
};
function _onceWrap(target, type, listener) {
let fired = false;
function wrapper(...args) {
if (fired) return;
fired = true;
target.removeListener(type, wrapper);
return ReflectApply(listener, target, args);
}
wrapper.listener = listener;
return wrapper;
}
/**
* Adds a one-time `listener` function to the event emitter.
* @param {string | symbol} type
* @param {Function} listener
* @returns {EventEmitter}
*/
EventEmitter.prototype.once = function once(type, listener) {
checkListener(listener);
this.on(type, _onceWrap(this, type, listener));
return this;
};
/**
* Adds a one-time `listener` function to the beginning of
* the listeners array.
* @param {string | symbol} type
* @param {Function} listener
* @returns {EventEmitter}
*/
EventEmitter.prototype.prependOnceListener =
function prependOnceListener(type, listener) {
checkListener(listener);
this.prependListener(type, _onceWrap(this, type, listener));
return this;
};
/**
* Removes the specified `listener` from the listeners array.
* @param {string | symbol} type
* @param {Function} listener
* @returns {EventEmitter}
*/
EventEmitter.prototype.removeListener =
function removeListener(type, listener) {
checkListener(listener);
const events = this._events;
if (events === undefined)
return this;
let list = events[type];
if (list === undefined)
return this;
if (list === listener || list.listener === listener) {
this._eventsCount -= 1;
if (this[kShapeMode]) {
events[type] = undefined;
} else if (this._eventsCount === 0) {
this._events = { __proto__: null };
} else {
delete events[type];
}
if (events.removeListener !== undefined)
this.emit('removeListener', type, list.listener || listener);
} else if (typeof list !== 'function') {
list = ensureMutableListenerArray(events, type, list);
let position = -1;
for (let i = list.length - 1; i >= 0; i--) {
if (list[i] === listener || list[i].listener === listener) {
position = i;
break;
}
}
if (position < 0)
return this;
if (position === 0)
list.shift();
else {
spliceOne(list, position);
}
if (list.length === 1)
events[type] = list[0];
if (events.removeListener !== undefined)
this.emit('removeListener', type, listener);
}
return this;
};
EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
/**
* Removes all listeners from the event emitter. (Only
* removes listeners for a specific event name if specified
* as `type`).
* @param {string | symbol} [type]
* @returns {EventEmitter}
*/
EventEmitter.prototype.removeAllListeners =
function removeAllListeners(type) {
const events = this._events;
if (events === undefined)
return this;
// Not listening for removeListener, no need to emit
if (events.removeListener === undefined) {
if (arguments.length === 0) {
this._events = { __proto__: null };
this._eventsCount = 0;
} else if (events[type] !== undefined) {
if (--this._eventsCount === 0)
this._events = { __proto__: null };
else
delete events[type];
}
this[kShapeMode] = false;
return this;
}
// Emit removeListener for all listeners on all events
if (arguments.length === 0) {
for (const key of ReflectOwnKeys(events)) {
if (key === 'removeListener') continue;
this.removeAllListeners(key);
}
this.removeAllListeners('removeListener');
this._events = { __proto__: null };
this._eventsCount = 0;
this[kShapeMode] = false;
return this;
}
const listeners = events[type];
if (typeof listeners === 'function') {
this.removeListener(type, listeners);
} else if (listeners !== undefined) {
// LIFO order
for (let i = listeners.length - 1; i >= 0; i--) {
this.removeListener(type, listeners[i]);
}
}
return this;
};
function _listeners(target, type, unwrap) {
const events = target._events;
if (events === undefined)
return [];
const evlistener = events[type];
if (evlistener === undefined)
return [];
if (typeof evlistener === 'function')
return unwrap ? [evlistener.listener || evlistener] : [evlistener];
return unwrap ?
unwrapListeners(evlistener) : arrayClone(evlistener);
}
/**
* Returns a copy of the array of listeners for the event name
* specified as `type`.
* @param {string | symbol} type
* @returns {Function[]}
*/
EventEmitter.prototype.listeners = function listeners(type) {
return _listeners(this, type, true);
};
/**
* Returns a copy of the array of listeners and wrappers for
* the event name specified as `type`.
* @param {string | symbol} type
* @returns {Function[]}
*/
EventEmitter.prototype.rawListeners = function rawListeners(type) {
return _listeners(this, type, false);
};
/**
* Returns the number of listeners listening to event name
* specified as `type`.
* @param {string | symbol} type
* @param {Function} [listener]
* @returns {number}
*/
EventEmitter.prototype.listenerCount = function listenerCount(type, listener) {
const events = this._events;
if (events !== undefined) {
const evlistener = events[type];
if (typeof evlistener === 'function') {
if (listener != null) {
return listener === evlistener || listener === evlistener.listener ? 1 : 0;
}
return 1;
} else if (evlistener !== undefined) {
if (listener != null) {
let matching = 0;
for (let i = 0, l = evlistener.length; i < l; i++) {
if (evlistener[i] === listener || evlistener[i].listener === listener) {
matching++;
}
}
return matching;
}
return evlistener.length;
}
}
return 0;
};
/**
* Returns an array listing the events for which
* the emitter has registered listeners.
* @returns {(string | symbol)[]}
*/
EventEmitter.prototype.eventNames = function eventNames() {
if (this._eventsCount === 0)
return [];
const events = this._events;
const names = [];
for (const key of ReflectOwnKeys(events)) {
// Removed listeners leave the key in place with an `undefined` value.
if (events[key] !== undefined)
ArrayPrototypePush(names, key);
}
return names;
};
function arrayClone(arr) {
// At least since V8 8.3, this implementation is faster than the previous
// which always used a simple for-loop
switch (arr.length) {
case 2: return [arr[0], arr[1]];
case 3: return [arr[0], arr[1], arr[2]];
case 4: return [arr[0], arr[1], arr[2], arr[3]];
case 5: return [arr[0], arr[1], arr[2], arr[3], arr[4]];
case 6: return [arr[0], arr[1], arr[2], arr[3], arr[4], arr[5]];
}
return ArrayPrototypeSlice(arr);
}
function cloneEventListenerArray(arr) {
const copy = arrayClone(arr);
copy[kEmitting] = 0;
if (arr.warned) {
copy.warned = true;
}
return copy;
}
function ensureMutableListenerArray(events, type, handler) {
if (handler[kEmitting] > 0) {
const copy = cloneEventListenerArray(handler);
events[type] = copy;
return copy;
}
return handler;
}
function unwrapListeners(arr) {
const ret = arrayClone(arr);
for (let i = 0; i < ret.length; ++i) {
const orig = ret[i].listener;
if (typeof orig === 'function')
ret[i] = orig;
}
return ret;
}
/**
* Returns a copy of the array of listeners for the event name
* specified as `type`.
* @param {EventEmitter | EventTarget} emitterOrTarget
* @param {string | symbol} type
* @returns {Function[]}
*/
function getEventListeners(emitterOrTarget, type) {
// First check if EventEmitter
if (typeof emitterOrTarget.listeners === 'function') {
return emitterOrTarget.listeners(type);
}
// Require event target lazily to avoid always loading it
const { isEventTarget, kEvents } = require('internal/event_target');
if (isEventTarget(emitterOrTarget)) {
const root = emitterOrTarget[kEvents]?.get(type);
const listeners = [];
let handler = root?.next;
while (handler?.listener !== undefined) {
const listener = handler.listener?.deref ?
handler.listener.deref() : handler.listener;
listeners.push(listener);
handler = handler.next;
}
return listeners;
}
throw new ERR_INVALID_ARG_TYPE('emitter',
['EventEmitter', 'EventTarget'],
emitterOrTarget);
}
/**
* Returns the max listeners set.
* @param {EventEmitter | EventTarget} emitterOrTarget
* @returns {number}
*/
function getMaxListeners(emitterOrTarget) {
if (typeof emitterOrTarget?.getMaxListeners === 'function') {
return _getMaxListeners(emitterOrTarget);
} else if (typeof emitterOrTarget?.[kMaxEventTargetListeners] === 'number') {
return emitterOrTarget[kMaxEventTargetListeners];
}
throw new ERR_INVALID_ARG_TYPE('emitter',
['EventEmitter', 'EventTarget'],
emitterOrTarget);
}
/**
* Returns the number of registered listeners for `type`.
* @param {EventEmitter | EventTarget} emitterOrTarget
* @param {string | symbol} type
* @returns {number}
*/
function listenerCount(emitterOrTarget, type) {
if (typeof emitterOrTarget.listenerCount === 'function') {
return emitterOrTarget.listenerCount(type);
}
const { isEventTarget, kEvents } = require('internal/event_target');
if (isEventTarget(emitterOrTarget)) {
return emitterOrTarget[kEvents]?.get(type)?.size ?? 0;
}
throw new ERR_INVALID_ARG_TYPE('emitter',
['EventEmitter', 'EventTarget'],
emitterOrTarget);
}
/**
* Creates a `Promise` that is fulfilled when the emitter
* emits the given event.
* @param {EventEmitter} emitter
* @param {string | symbol} name
* @param {{ signal: AbortSignal; }} [options]
* @returns {Promise}
*/
async function once(emitter, name, options = kEmptyObject) {
validateObject(options, 'options');
const { signal } = options;
validateAbortSignal(signal, 'options.signal');
if (signal?.aborted)
throw new AbortError(undefined, { cause: signal.reason });
return new Promise((resolve, reject) => {
const errorListener = (err) => {
emitter.removeListener(name, resolver);
if (signal != null) {
eventTargetAgnosticRemoveListener(signal, 'abort', abortListener);
}
reject(err);
};
const resolver = (...args) => {
if (typeof emitter.removeListener === 'function') {
emitter.removeListener('error', errorListener);
}
if (signal != null) {
eventTargetAgnosticRemoveListener(signal, 'abort', abortListener);
}
resolve(args);
};
kResistStopPropagation ??= require('internal/event_target').kResistStopPropagation;
const opts = { __proto__: null, once: true, [kResistStopPropagation]: true };
eventTargetAgnosticAddListener(emitter, name, resolver, opts);
if (name !== 'error' && typeof emitter.once === 'function') {
// EventTarget does not have `error` event semantics like Node
// EventEmitters, we listen to `error` events only on EventEmitters.
emitter.once('error', errorListener);
}
function abortListener() {
eventTargetAgnosticRemoveListener(emitter, name, resolver);
eventTargetAgnosticRemoveListener(emitter, 'error', errorListener);
reject(new AbortError(undefined, { cause: signal?.reason }));
}
if (signal != null) {
eventTargetAgnosticAddListener(
signal, 'abort', abortListener, { __proto__: null, once: true, [kResistStopPropagation]: true });
}
});
}
function createIterResult(value, done) {
return { done, value };
}
function eventTargetAgnosticRemoveListener(emitter, name, listener, flags) {
if (typeof emitter.removeListener === 'function') {
emitter.removeListener(name, listener);
} else if (typeof emitter.removeEventListener === 'function') {
emitter.removeEventListener(name, listener, flags);
} else {
throw new ERR_INVALID_ARG_TYPE('emitter', 'EventEmitter', emitter);
}
}
function eventTargetAgnosticAddListener(emitter, name, listener, flags) {
if (typeof emitter.on === 'function') {
if (flags?.once) {
emitter.once(name, listener);
} else {
emitter.on(name, listener);
}
} else if (typeof emitter.addEventListener === 'function') {
emitter.addEventListener(name, listener, flags);
} else {
throw new ERR_INVALID_ARG_TYPE('emitter', 'EventEmitter', emitter);
}
}
/**
* Returns an `AsyncIterator` that iterates `event` events.
* @param {EventEmitter} emitter
* @param {string | symbol} event
* @param {{
* signal: AbortSignal;
* close?: string[];
* highWaterMark?: number,
* lowWaterMark?: number
* }} [options]
* @returns {AsyncIterator}
*/
function on(emitter, event, options = kEmptyObject) {
// Parameters validation
validateObject(options, 'options');
const signal = options.signal;
validateAbortSignal(signal, 'options.signal');
if (signal?.aborted)
throw new AbortError(undefined, { cause: signal.reason });
// Support both highWaterMark and highWatermark for backward compatibility
const highWatermark = options.highWaterMark ?? options.highWatermark ?? NumberMAX_SAFE_INTEGER;
validateInteger(highWatermark, 'options.highWaterMark', 1);
// Support both lowWaterMark and lowWatermark for backward compatibility
const lowWatermark = options.lowWaterMark ?? options.lowWatermark ?? 1;
validateInteger(lowWatermark, 'options.lowWaterMark', 1);
// Preparing controlling queues and variables
FixedQueue ??= require('internal/fixed_queue');
const unconsumedEvents = new FixedQueue();
const unconsumedPromises = new FixedQueue();
let paused = false;
let error = null;
let finished = false;
let size = 0;
const iterator = ObjectSetPrototypeOf({
next() {
// First, we consume all unread events
if (size) {
const value = unconsumedEvents.shift();
size--;
if (paused && size < lowWatermark) {
emitter.resume(); // Can not be finished yet
paused = false;
}
return PromiseResolve(createIterResult(value, false));
}
// Then we error, if an error happened
// This happens one time if at all, because after 'error'
// we stop listening
if (error) {
const p = PromiseReject(error);
// Only the first element errors
error = null;
return p;
}
// If the iterator is finished, resolve to done
if (finished) return closeHandler();
// Wait until an event happens
return new Promise(function(resolve, reject) {
unconsumedPromises.push({ resolve, reject });
});
},
return() {
return closeHandler();
},
throw(err) {
if (!err || !(err instanceof Error)) {
throw new ERR_INVALID_ARG_TYPE('EventEmitter.AsyncIterator',
'Error', err);
}
errorHandler(err);
},
[SymbolAsyncIterator]() {
return this;
},
[kWatermarkData]: {
/**
* The current queue size
* @returns {number}
*/
get size() {
return size;
},
/**
* The low watermark. The emitter is resumed every time size is lower than it
* @returns {number}
*/
get low() {
return lowWatermark;
},
/**
* The high watermark. The emitter is paused every time size is higher than it
* @returns {number}
*/
get high() {
return highWatermark;
},
/**
* It checks whether the emitter is paused by the watermark controller or not
* @returns {boolean}
*/
get isPaused() {
return paused;
},
},
}, AsyncIteratorPrototype);
// Adding event handlers
const { addEventListener, removeAll } = listenersController();
kFirstEventParam ??= require('internal/events/symbols').kFirstEventParam;
addEventListener(emitter, event, options[kFirstEventParam] ? eventHandler : function(...args) {
return eventHandler(args);
});
if (event !== 'error' && typeof emitter.on === 'function') {
addEventListener(emitter, 'error', errorHandler);
}
const closeEvents = options?.close;
if (closeEvents?.length) {
for (let i = 0; i < closeEvents.length; i++) {
addEventListener(emitter, closeEvents[i], closeHandler);
}
}
const abortListenerDisposable = signal ? addAbortListener(signal, abortListener) : null;
return iterator;
function abortListener() {
errorHandler(new AbortError(undefined, { cause: signal?.reason }));
}
function eventHandler(value) {
if (unconsumedPromises.isEmpty()) {
size++;
if (!paused && size > highWatermark) {
paused = true;
emitter.pause();
}
unconsumedEvents.push(value);
} else unconsumedPromises.shift().resolve(createIterResult(value, false));
}
function errorHandler(err) {
if (unconsumedPromises.isEmpty()) error = err;
else unconsumedPromises.shift().reject(err);
closeHandler();
}
function closeHandler() {
abortListenerDisposable?.[SymbolDispose]();
removeAll();
finished = true;
paused = false;
const doneResult = createIterResult(undefined, true);
while (!unconsumedPromises.isEmpty()) {
unconsumedPromises.shift().resolve(doneResult);
}
return PromiseResolve(doneResult);
}
}
function listenersController() {
const listeners = [];
return {
addEventListener(emitter, event, handler, flags) {
eventTargetAgnosticAddListener(emitter, event, handler, flags);
ArrayPrototypePush(listeners, [emitter, event, handler, flags]);
},
removeAll() {
while (listeners.length > 0) {
ReflectApply(eventTargetAgnosticRemoveListener, undefined, ArrayPrototypePop(listeners));
}
},
};
}