Skip to content

Commit

Permalink
events: add isOnce arg to EventEmitter listeners
Browse files Browse the repository at this point in the history
`isOnce` will be true when the new or removed listener was added using
the `EventEmitter.once()` method.
  • Loading branch information
santigimeno committed Nov 15, 2017
1 parent 1b093cb commit 9482fd3
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 14 deletions.
2 changes: 2 additions & 0 deletions doc/api/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ added: v0.1.26

* `eventName` {any} The name of the event being listened for
* `listener` {Function} The event handler function
* `isOnce` {boolean} If `true` the `listener` was added via the `once()` method

The `EventEmitter` instance will emit its own `'newListener'` event *before*
a listener is added to its internal array of listeners.
Expand Down Expand Up @@ -233,6 +234,7 @@ changes:

* `eventName` {any} The event name
* `listener` {Function} The event handler function
* `isOnce` {boolean} If `true` the `listener` was added via the `once()` method

The `'removeListener'` event is emitted *after* the `listener` is removed.

Expand Down
19 changes: 14 additions & 5 deletions lib/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,8 @@ function _addListener(target, type, listener, prepend) {
// adding it to the listeners, first emit "newListener".
if (events.newListener !== undefined) {
target.emit('newListener', type,
listener.listener ? listener.listener : listener);
listener.listener || listener,
!!listener.listener);

// Re-assign `events` because a newListener handler could have caused the
// this._events to be assigned to a new object
Expand Down Expand Up @@ -307,8 +308,12 @@ EventEmitter.prototype.removeListener =
this._events = Object.create(null);
else {
delete events[type];
if (events.removeListener)
this.emit('removeListener', type, list.listener || listener);
if (events.removeListener) {
this.emit('removeListener',
type,
list.listener || listener,
!!list.listener);
}
}
} else if (typeof list !== 'function') {
position = -1;
Expand All @@ -335,8 +340,12 @@ EventEmitter.prototype.removeListener =
if (list.length === 1)
events[type] = list[0];

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

return this;
Expand Down
5 changes: 4 additions & 1 deletion test/parallel/test-event-emitter-add-listeners.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,19 @@ const EventEmitter = require('events');
const ee = new EventEmitter();
const events_new_listener_emitted = [];
const listeners_new_listener_emitted = [];
const is_once_new_listener_emitted = [];

// Sanity check
assert.strictEqual(ee.addListener, ee.on);

ee.on('newListener', function(event, listener) {
ee.on('newListener', function(event, listener, isOnce) {
// Don't track newListener listeners.
if (event === 'newListener')
return;

events_new_listener_emitted.push(event);
listeners_new_listener_emitted.push(listener);
is_once_new_listener_emitted.push(isOnce);
});

const hello = common.mustCall(function(a, b) {
Expand All @@ -56,6 +58,7 @@ const EventEmitter = require('events');
ee.once('foo', assert.fail);
assert.deepStrictEqual(['hello', 'foo'], events_new_listener_emitted);
assert.deepStrictEqual([hello, assert.fail], listeners_new_listener_emitted);
assert.deepStrictEqual([false, true], is_once_new_listener_emitted);

ee.emit('hello', 'a', 'b');
}
Expand Down
17 changes: 15 additions & 2 deletions test/parallel/test-event-emitter-remove-all-listeners.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,17 +87,30 @@ function expect(expected) {
{
const ee = new events.EventEmitter();
let expectLength = 2;
ee.on('removeListener', function(name, noop) {
ee.on('removeListener', function(name, noop, isOnce) {
assert.strictEqual(expectLength--, this.listeners('baz').length);
assert.strictEqual(isOnce, expectLength === 1);
});
ee.on('baz', common.mustNotCall());
ee.on('baz', common.mustNotCall());
ee.on('baz', common.mustNotCall());
ee.once('baz', common.mustNotCall());
assert.strictEqual(ee.listeners('baz').length, expectLength + 1);
ee.removeAllListeners('baz');
assert.strictEqual(ee.listeners('baz').length, 0);
}

{
const ee = new events.EventEmitter();
ee.on('removeListener', function(name, noop, isOnce) {
assert.strictEqual(name, 'baz');
assert.strictEqual(isOnce, true);
assert.strictEqual(ee.listeners('baz').length, 0);
});

ee.once('baz', common.mustNotCall());
ee.removeAllListeners('baz');
}

{
const ee = new events.EventEmitter();
assert.deepStrictEqual(ee, ee.removeAllListeners());
Expand Down
32 changes: 26 additions & 6 deletions test/parallel/test-event-emitter-remove-listeners.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ function listener2() {}
{
const ee = new EventEmitter();
ee.on('hello', listener1);
ee.on('removeListener', common.mustCall((name, cb) => {
ee.on('removeListener', common.mustCall((name, cb, isOnce) => {
assert.strictEqual(name, 'hello');
assert.strictEqual(cb, listener1);
assert.strictEqual(isOnce, false);
}));
ee.removeListener('hello', listener1);
assert.deepStrictEqual([], ee.listeners('hello'));
Expand All @@ -50,16 +51,18 @@ function listener2() {}
const ee = new EventEmitter();
ee.on('hello', listener1);
ee.on('hello', listener2);
ee.once('removeListener', common.mustCall((name, cb) => {
ee.once('removeListener', common.mustCall((name, cb, isOnce) => {
assert.strictEqual(name, 'hello');
assert.strictEqual(cb, listener1);
assert.strictEqual(isOnce, false);
assert.deepStrictEqual([listener2], ee.listeners('hello'));
}));
ee.removeListener('hello', listener1);
assert.deepStrictEqual([listener2], ee.listeners('hello'));
ee.once('removeListener', common.mustCall((name, cb) => {
ee.once('removeListener', common.mustCall((name, cb, isOnce) => {
assert.strictEqual(name, 'hello');
assert.strictEqual(cb, listener2);
assert.strictEqual(isOnce, false);
assert.deepStrictEqual([], ee.listeners('hello'));
}));
ee.removeListener('hello', listener2);
Expand Down Expand Up @@ -91,13 +94,15 @@ function listener2() {}
const ee = new EventEmitter();
ee.on('hello', listener1);
ee.on('hello', listener2);
ee.once('removeListener', common.mustCall((name, cb) => {
ee.once('removeListener', common.mustCall((name, cb, isOnce) => {
assert.strictEqual(name, 'hello');
assert.strictEqual(cb, listener1);
assert.strictEqual(isOnce, false);
assert.deepStrictEqual([listener2], ee.listeners('hello'));
ee.once('removeListener', common.mustCall((name, cb) => {
ee.once('removeListener', common.mustCall((name, cb, isOnce) => {
assert.strictEqual(name, 'hello');
assert.strictEqual(cb, listener2);
assert.strictEqual(isOnce, false);
assert.deepStrictEqual([], ee.listeners('hello'));
}));
ee.removeListener('hello', listener2);
Expand Down Expand Up @@ -126,13 +131,28 @@ function listener2() {}
ee.emit('hello');
}

{
const ee = new EventEmitter();

const listener = common.mustCall((eventName, listener, isOnce) => {
assert.strictEqual(eventName, 'hello');
assert.strictEqual(listener, listener1);
assert.strictEqual(isOnce, true);
});

ee.on('removeListener', listener);
ee.once('hello', listener1);
ee.removeListener('hello', listener1);
}

{
const ee = new EventEmitter();

ee.once('hello', listener1);
ee.on('removeListener', common.mustCall((eventName, listener) => {
ee.on('removeListener', common.mustCall((eventName, listener, isOnce) => {
assert.strictEqual(eventName, 'hello');
assert.strictEqual(listener, listener1);
assert.strictEqual(isOnce, true);
}));
ee.emit('hello');
}
Expand Down

0 comments on commit 9482fd3

Please sign in to comment.