Skip to content
Closed
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 75 additions & 47 deletions lib/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,15 @@ const {
PromiseReject,
PromiseResolve,
ReflectApply,
ReflectOwnKeys,
String,
ObjectFromEntries,
ArrayFrom,
StringPrototypeSplit,
Symbol,
SymbolFor,
SymbolAsyncIterator,
SymbolDispose,
SafeMap,
} = primordials;
const kRejection = SymbolFor('nodejs.rejection');

Expand Down Expand Up @@ -85,6 +87,7 @@ const {
validateString,
} = require('internal/validators');

const kEvents = Symbol('kEvents');
const kCapture = Symbol('kCapture');
const kErrorMonitor = Symbol('events.errorMonitor');
const kMaxEventTargetListeners = Symbol('events.maxEventTargetListeners');
Expand Down Expand Up @@ -262,8 +265,7 @@ ObjectDefineProperty(EventEmitter.prototype, kCapture, {
enumerable: false,
});

EventEmitter.prototype._events = undefined;
EventEmitter.prototype._eventsCount = 0;
EventEmitter.prototype[kEvents] = undefined;
EventEmitter.prototype._maxListeners = undefined;

// By default EventEmitters will print a warning if more than 10 listeners are
Expand All @@ -287,6 +289,37 @@ ObjectDefineProperty(EventEmitter, 'defaultMaxListeners', {
},
});

// _events and _eventsCount are Legacy
ObjectDefineProperty(EventEmitter.prototype, '_events', {
__proto__: null,
enumerable: true,
get: function() {
// TODO - maybe in order to not break the ecosystem, create a Proxy that will update the events map
const events = this[kEvents];
if (events === undefined) {
return undefined;
}

return ObjectFromEntries(events.entries());
},
set: function(events) {
this[kEvents] = new SafeMap(events);
},
});

ObjectDefineProperty(EventEmitter.prototype, '_eventsCount', {
__proto__: null,
enumerable: true,
get: function() {
const events = this[kEvents];
if (events === undefined) {
return 0;
}

return events.size;
},
});

ObjectDefineProperties(EventEmitter, {
kMaxEventTargetListeners: {
__proto__: null,
Expand Down Expand Up @@ -339,11 +372,9 @@ EventEmitter.setMaxListeners =
// 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;
if (this[kEvents] === undefined ||
this[kEvents] === ObjectGetPrototypeOf(this)[kEvents]) {
this[kEvents] = new SafeMap();
}

this._maxListeners = this._maxListeners || undefined;
Expand Down Expand Up @@ -462,11 +493,11 @@ function enhanceStackTrace(err, own) {
EventEmitter.prototype.emit = function emit(type, ...args) {
let doError = (type === 'error');

const events = this._events;
const events = this[kEvents];
if (events !== undefined) {
if (doError && events[kErrorMonitor] !== undefined)
if (doError && events.has(kErrorMonitor))
this.emit(kErrorMonitor, ...args);
doError = (doError && events.error === undefined);
doError = (doError && !events.has('error'));
} else if (!doError)
return false;

Expand Down Expand Up @@ -506,7 +537,7 @@ EventEmitter.prototype.emit = function emit(type, ...args) {
throw err; // Unhandled 'error' event
}

const handler = events[type];
const handler = events.get(type);

if (handler === undefined)
return false;
Expand Down Expand Up @@ -547,33 +578,32 @@ function _addListener(target, type, listener, prepend) {

checkListener(listener);

events = target._events;
events = target[kEvents];
if (events === undefined) {
events = target._events = { __proto__: null };
target._eventsCount = 0;
events = target[kEvents] = new SafeMap();
} 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);

// TODO - revisit this comment, user can set it to new object?
// Re-assign `events` because a newListener handler could have caused the
// this._events to be assigned to a new object
events = target._events;
// this[kEvents] to be assigned to a new object
events = target[kEvents];
}
existing = events[type];
existing = events.get(type);
}

if (existing === undefined) {
// Optimize the case of one listener. Don't need the extra array object.
events[type] = listener;
++target._eventsCount;
events.set(type, listener);
} else {
if (typeof existing === 'function') {
// Adding the second element, need to change to array.
existing = events[type] =
prepend ? [listener, existing] : [existing, listener];
existing = prepend ? [listener, existing] : [existing, listener];
events.set(type, existing);
// If we've already got an array, just append.
} else if (prepend) {
existing.unshift(listener);
Expand Down Expand Up @@ -677,20 +707,20 @@ EventEmitter.prototype.removeListener =
function removeListener(type, listener) {
checkListener(listener);

const events = this._events;
const events = this[kEvents];
if (events === undefined)
return this;

const list = events[type];
const list = events.get(type);
if (list === undefined)
return this;

if (list === listener || list.listener === listener) {
if (--this._eventsCount === 0)
this._events = { __proto__: null };
if (this[kEvents].size === 1)
this[kEvents] = new SafeMap();
else {
delete events[type];
if (events.removeListener)
events.delete(type);
if (events.has('removeListener'))
this.emit('removeListener', type, list.listener || listener);
}
} else if (typeof list !== 'function') {
Expand All @@ -715,9 +745,9 @@ EventEmitter.prototype.removeListener =
}

if (list.length === 1)
events[type] = list[0];
events.set(type, list[0]);

if (events.removeListener !== undefined)
if (events.has('removeListener'))
this.emit('removeListener', type, listener);
}

Expand All @@ -735,37 +765,35 @@ EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
*/
EventEmitter.prototype.removeAllListeners =
function removeAllListeners(type) {
const events = this._events;
const events = this[kEvents];
if (events === undefined)
return this;

// Not listening for removeListener, no need to emit
if (events.removeListener === undefined) {
if (!events.has('removeListener')) {
if (arguments.length === 0) {
this._events = { __proto__: null };
this._eventsCount = 0;
} else if (events[type] !== undefined) {
if (--this._eventsCount === 0)
this._events = { __proto__: null };
this[kEvents] = new SafeMap();
} else if (events.has(type)) {
if (this[kEvents].size === 1)
this[kEvents] = new SafeMap();
else
delete events[type];
this[kEvents].delete(type);
}
return this;
}

// Emit removeListener for all listeners on all events
if (arguments.length === 0) {
for (const key of ReflectOwnKeys(events)) {
for (const key of events.keys()) {
if (key === 'removeListener') continue;
this.removeAllListeners(key);
}
this.removeAllListeners('removeListener');
this._events = { __proto__: null };
this._eventsCount = 0;
this[kEvents] = new SafeMap();
return this;
}

const listeners = events[type];
const listeners = events.get(type);

if (typeof listeners === 'function') {
this.removeListener(type, listeners);
Expand All @@ -780,12 +808,12 @@ EventEmitter.prototype.removeAllListeners =
};

function _listeners(target, type, unwrap) {
const events = target._events;
const events = target[kEvents];

if (events === undefined)
return [];

const evlistener = events[type];
const evlistener = events.get(type);
if (evlistener === undefined)
return [];

Expand Down Expand Up @@ -841,10 +869,10 @@ EventEmitter.prototype.listenerCount = listenerCount;
* @returns {number}
*/
function listenerCount(type, listener) {
const events = this._events;
const events = this[kEvents];

if (events !== undefined) {
const evlistener = events[type];
const evlistener = events.get(type);

if (typeof evlistener === 'function') {
if (listener != null) {
Expand Down Expand Up @@ -878,7 +906,7 @@ function listenerCount(type, listener) {
* @returns {any[]}
*/
EventEmitter.prototype.eventNames = function eventNames() {
return this._eventsCount > 0 ? ReflectOwnKeys(this._events) : [];
return this[kEvents].size > 0 ? ArrayFrom(this[kEvents].keys()) : [];
};

function arrayClone(arr) {
Expand Down