Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

events: add/removeListener should call on/off #29503

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from 6 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
170 changes: 87 additions & 83 deletions doc/api/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -290,8 +290,10 @@ added: v0.1.26
-->
* `eventName` {string|symbol}
* `listener` {Function}
* Returns: {EventEmitter}

Alias for `emitter.on(eventName, listener)`.
This function is implemented by calling [`emitter.on()`][].
When overriding, override [`emitter.on()`][].

### emitter.emit(eventName[, ...args])
<!-- YAML
Expand Down Expand Up @@ -415,7 +417,86 @@ added: v10.0.0
* `listener` {Function}
* Returns: {EventEmitter}

Alias for [`emitter.removeListener()`][].
Removes the specified `listener` from the listener array for the event named
`eventName`.

```js
const callback = (stream) => {
console.log('someone connected!');
};
server.on('connection', callback);
// ...
server.off('connection', callback);
```

`off()` will remove, at most, one instance of a listener from the
listener array. If any single listener has been added multiple times to the
listener array for the specified `eventName`, then `off()` must be
called multiple times to remove each instance.

Once an event has been emitted, all listeners attached to it at the
time of emitting will be called in order. This implies that any
`off()` or `removeAllListeners()` calls *after* emitting and
*before* the last listener finishes execution will not remove them from
`emit()` in progress. Subsequent events will behave as expected.

```js
const myEmitter = new MyEmitter();

const callbackA = () => {
console.log('A');
myEmitter.off('event', callbackB);
};

const callbackB = () => {
console.log('B');
};

myEmitter.on('event', callbackA);

myEmitter.on('event', callbackB);

// callbackA removes listener callbackB but it will still be called.
// Internal listener array at time of emit [callbackA, callbackB]
myEmitter.emit('event');
// Prints:
// A
// B

// callbackB is now removed.
// Internal listener array [callbackA]
myEmitter.emit('event');
// Prints:
// A
```

Because listeners are managed using an internal array, calling this will
change the position indices of any listener registered *after* the listener
being removed. This will not impact the order in which listeners are called,
but it means that any copies of the listener array as returned by
the `emitter.listeners()` method will need to be recreated.

When a single function has been added as a handler multiple times for a single
event (as in the example below), `off()` will remove the most
recently added instance. In the example the `once('ping')`
listener is removed:

```js
const ee = new EventEmitter();

function pong() {
console.log('pong');
}

ee.on('ping', pong);
ee.once('ping', pong);
ee.off('ping', pong);

ee.emit('ping');
ee.emit('ping');
```

Returns a reference to the `EventEmitter`, so that calls can be chained.

### emitter.on(eventName, listener)
<!-- YAML
Expand Down Expand Up @@ -555,86 +636,8 @@ added: v0.1.26
* `listener` {Function}
* Returns: {EventEmitter}

Removes the specified `listener` from the listener array for the event named
`eventName`.

```js
const callback = (stream) => {
console.log('someone connected!');
};
server.on('connection', callback);
// ...
server.removeListener('connection', callback);
```

`removeListener()` will remove, at most, one instance of a listener from the
listener array. If any single listener has been added multiple times to the
listener array for the specified `eventName`, then `removeListener()` must be
called multiple times to remove each instance.

Once an event has been emitted, all listeners attached to it at the
time of emitting will be called in order. This implies that any
`removeListener()` or `removeAllListeners()` calls *after* emitting and
*before* the last listener finishes execution will not remove them from
`emit()` in progress. Subsequent events will behave as expected.

```js
const myEmitter = new MyEmitter();

const callbackA = () => {
console.log('A');
myEmitter.removeListener('event', callbackB);
};

const callbackB = () => {
console.log('B');
};

myEmitter.on('event', callbackA);

myEmitter.on('event', callbackB);

// callbackA removes listener callbackB but it will still be called.
// Internal listener array at time of emit [callbackA, callbackB]
myEmitter.emit('event');
// Prints:
// A
// B

// callbackB is now removed.
// Internal listener array [callbackA]
myEmitter.emit('event');
// Prints:
// A
```

Because listeners are managed using an internal array, calling this will
change the position indices of any listener registered *after* the listener
being removed. This will not impact the order in which listeners are called,
but it means that any copies of the listener array as returned by
the `emitter.listeners()` method will need to be recreated.

When a single function has been added as a handler multiple times for a single
event (as in the example below), `removeListener()` will remove the most
recently added instance. In the example the `once('ping')`
listener is removed:

```js
const ee = new EventEmitter();

function pong() {
console.log('pong');
}

ee.on('ping', pong);
ee.once('ping', pong);
ee.removeListener('ping', pong);

ee.emit('ping');
ee.emit('ping');
```

Returns a reference to the `EventEmitter`, so that calls can be chained.
This function is implemented by calling [`emitter.off()`][].
When overriding, override [`emitter.off()`][].

### emitter.setMaxListeners(n)
<!-- YAML
Expand Down Expand Up @@ -731,7 +734,8 @@ run();
[`EventEmitter.defaultMaxListeners`]: #events_eventemitter_defaultmaxlisteners
[`domain`]: domain.html
[`emitter.listenerCount()`]: #events_emitter_listenercount_eventname
[`emitter.removeListener()`]: #events_emitter_removelistener_eventname_listener
[`emitter.on()`]: #events_emitter_on_eventname_listener
[`emitter.off()`]: #events_emitter_off_eventname_listener
[`emitter.setMaxListeners(n)`]: #events_emitter_setmaxlisteners_n
[`fs.ReadStream`]: fs.html#fs_class_fs_readstream
[`net.Server`]: net.html#net_class_net_server
Expand Down
2 changes: 0 additions & 2 deletions lib/_http_server.js
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,6 @@ function connectionListenerInternal(server, socket) {

// Overrides to unconsume on `data`, `readable` listeners
socket.on = generateSocketListenerWrapper('on');
socket.addListener = generateSocketListenerWrapper('addListener');
socket.prependListener = generateSocketListenerWrapper('prependListener');

// We only consume the socket if it has never been consumed before.
Expand Down Expand Up @@ -792,7 +791,6 @@ function generateSocketListenerWrapper(originalFnName) {
const res = net.Socket.prototype[originalFnName].call(this, ev, fn);
if (!this.parser) {
this.on = net.Socket.prototype.on;
this.addListener = net.Socket.prototype.addListener;
this.prependListener = net.Socket.prototype.prependListener;
return res;
}
Expand Down
5 changes: 2 additions & 3 deletions lib/_stream_readable.js
Original file line number Diff line number Diff line change
Expand Up @@ -897,10 +897,9 @@ Readable.prototype.on = function(ev, fn) {

return res;
};
Readable.prototype.addListener = Readable.prototype.on;

Readable.prototype.removeListener = function(ev, fn) {
const res = Stream.prototype.removeListener.call(this, ev, fn);
Readable.prototype.off = function(ev, fn) {
const res = Stream.prototype.off.call(this, ev, fn);

if (ev === 'readable') {
// We need to check if there is someone still listening to
Expand Down
15 changes: 10 additions & 5 deletions lib/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -280,10 +280,12 @@ function _addListener(target, type, listener, prepend) {
}

EventEmitter.prototype.addListener = function addListener(type, listener) {
return _addListener(this, type, listener, false);
return this.on(type, listener);
};

EventEmitter.prototype.on = EventEmitter.prototype.addListener;
EventEmitter.prototype.on = function on(type, listener) {
return _addListener(this, type, listener, false);
};

EventEmitter.prototype.prependListener =
function prependListener(type, listener) {
Expand Down Expand Up @@ -324,8 +326,8 @@ EventEmitter.prototype.prependOnceListener =
};

// Emits a 'removeListener' event if and only if the listener was removed.
EventEmitter.prototype.removeListener =
function removeListener(type, listener) {
EventEmitter.prototype.off =
function off(type, listener) {
let originalListener;

checkListener(listener);
Expand Down Expand Up @@ -378,7 +380,10 @@ EventEmitter.prototype.removeListener =
return this;
};

EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
EventEmitter.prototype.removeListener =
function removeListener(type, listener) {
return this.off(type, listener);
};

EventEmitter.prototype.removeAllListeners =
function removeAllListeners(type) {
Expand Down
3 changes: 0 additions & 3 deletions test/parallel/test-event-emitter-add-listeners.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,6 @@ const EventEmitter = require('events');
const events_new_listener_emitted = [];
const listeners_new_listener_emitted = [];

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

ee.on('newListener', function(event, listener) {
// Don't track newListener listeners.
if (event === 'newListener')
Expand Down
5 changes: 2 additions & 3 deletions test/parallel/test-event-emitter-method-names.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,9 @@ const events = require('events');

const E = events.EventEmitter.prototype;
assert.strictEqual(E.constructor.name, 'EventEmitter');
assert.strictEqual(E.on, E.addListener); // Same method.
assert.strictEqual(E.off, E.removeListener); // Same method.

Object.getOwnPropertyNames(E).forEach(function(name) {
if (name === 'constructor' || name === 'on' || name === 'off') return;
if (name === 'constructor') return;
if (typeof E[name] !== 'function') return;
assert.strictEqual(E[name].name, name);
});
16 changes: 16 additions & 0 deletions test/parallel/test-events-override.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
'use strict';

const common = require('../common');
const { EventEmitter: EE } = require('events');

{
const e = new EE();
e.on = common.mustCall();
e.addListener('test', () => {});
}

{
const e = new EE();
e.off = common.mustCall();
e.removeListener('test', () => {});
}