Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
63 changes: 45 additions & 18 deletions lib/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ 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');
Expand Down Expand Up @@ -514,19 +515,22 @@ EventEmitter.prototype.emit = function emit(type, ...args) {
addCatch(this, result, type, args);
}
} else {
const len = handler.length;
const listeners = arrayClone(handler);
for (let i = 0; i < len; ++i) {
const result = ReflectApply(listeners[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);
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]--;
}
}

Expand Down Expand Up @@ -565,13 +569,17 @@ function _addListener(target, type, listener, prepend) {
} 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];
existing[kEmitting] = 0;
events[type] = existing;
// If we've already got an array, just append.
} else if (prepend) {
existing.unshift(listener);
} else {
existing.push(listener);
existing = ensureMutableListenerArray(events, type, existing);
if (prepend) {
existing.unshift(listener);
} else {
existing.push(listener);
}
}

// Check for listener leak
Expand Down Expand Up @@ -674,7 +682,7 @@ EventEmitter.prototype.removeListener =
if (events === undefined)
return this;

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

Expand All @@ -692,6 +700,7 @@ EventEmitter.prototype.removeListener =
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--) {
Expand Down Expand Up @@ -875,6 +884,24 @@ function arrayClone(arr) {
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) {
Expand Down
18 changes: 18 additions & 0 deletions test/parallel/test-event-emitter-modify-in-emit.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,21 @@ assert.strictEqual(e.listeners('foo').length, 2);
e.emit('foo');
assert.deepStrictEqual(['callback2', 'callback3'], callbacks_called);
assert.strictEqual(e.listeners('foo').length, 0);

// Verify that removing all callbacks while in emit allows the current emit to
// propagate to all listeners.
callbacks_called = [];

function callback4() {
callbacks_called.push('callback4');
e.removeAllListeners('foo');
}

e.on('foo', callback4);
e.on('foo', callback2);
e.on('foo', callback3);
assert.strictEqual(e.listeners('foo').length, 3);
e.emit('foo');
assert.deepStrictEqual(['callback4', 'callback2', 'callback3'],
callbacks_called);
assert.strictEqual(e.listeners('foo').length, 0);
Loading