diff --git a/doc/api/events.markdown b/doc/api/events.markdown index 692efa8155e..7998684e739 100644 --- a/doc/api/events.markdown +++ b/doc/api/events.markdown @@ -26,7 +26,7 @@ If there is no listener for it, then the default action is to print a stack trace and exit the program. All EventEmitters emit the event `'newListener'` when new listeners are -added. +added and `'removeListener'` when a listener is removed. ### emitter.addListener(event, listener) ### emitter.on(event, listener) diff --git a/lib/events.js b/lib/events.js index 9b26c566306..ee02f15af6c 100644 --- a/lib/events.js +++ b/lib/events.js @@ -191,6 +191,7 @@ EventEmitter.prototype.once = function(type, listener) { return this; }; +// emits a 'removeListener' event iff the listener was removed EventEmitter.prototype.removeListener = function(type, listener) { if ('function' !== typeof listener) { throw new Error('removeListener only takes instances of Function'); @@ -216,23 +217,41 @@ EventEmitter.prototype.removeListener = function(type, listener) { list.splice(position, 1); if (list.length == 0) delete this._events[type]; + this.emit('removeListener', type, listener); } else if (list === listener || (list.listener && list.listener === listener)) { delete this._events[type]; + this.emit('removeListener', type, listener); } return this; }; EventEmitter.prototype.removeAllListeners = function(type) { + if (!this._events) return this; + if (arguments.length === 0) { + for (var key in this._events) { + if (key === 'removeListener') continue; + this.removeAllListeners(key); + } + this.removeAllListeners('removeListener'); this._events = {}; return this; } - // does not use listeners(), so no side effect of creating _events[type] - if (type && this._events && this._events[type]) this._events[type] = null; + var listeners = this._events[type]; + if (isArray(listeners)) { + while (listeners.length) { + // LIFO order + this.removeListener(type, listeners[listeners.length - 1]); + } + } else if (listeners) { + this.removeListener(type, listeners); + } + this._events[type] = null; + return this; }; diff --git a/test/simple/test-event-emitter-remove-all-listeners.js b/test/simple/test-event-emitter-remove-all-listeners.js index 38cfb79c67e..2cd31ebd261 100644 --- a/test/simple/test-event-emitter-remove-all-listeners.js +++ b/test/simple/test-event-emitter-remove-all-listeners.js @@ -24,6 +24,17 @@ var assert = require('assert'); var events = require('events'); +function expect(expected) { + var actual = []; + process.on('exit', function() { + assert.deepEqual(actual.sort(), expected.sort()); + }); + function listener(name) { + actual.push(name) + } + return common.mustCall(listener, expected.length); +} + function listener() {} var e1 = new events.EventEmitter(); @@ -34,6 +45,7 @@ e1.on('baz', listener); var fooListeners = e1.listeners('foo'); var barListeners = e1.listeners('bar'); var bazListeners = e1.listeners('baz'); +e1.on('removeListener', expect(['bar', 'baz', 'baz'])); e1.removeAllListeners('bar'); e1.removeAllListeners('baz'); assert.deepEqual(e1.listeners('foo'), [listener]); @@ -52,6 +64,9 @@ assert.notEqual(e1.listeners('baz'), bazListeners); var e2 = new events.EventEmitter(); e2.on('foo', listener); e2.on('bar', listener); +// expect LIFO order +e2.on('removeListener', expect(['foo', 'bar', 'removeListener'])); +e2.on('removeListener', expect(['foo', 'bar'])); e2.removeAllListeners(); console.error(e2); assert.deepEqual([], e2.listeners('foo')); diff --git a/test/simple/test-event-emitter-remove-listeners.js b/test/simple/test-event-emitter-remove-listeners.js index 70526668c4d..78133a4f34d 100644 --- a/test/simple/test-event-emitter-remove-listeners.js +++ b/test/simple/test-event-emitter-remove-listeners.js @@ -23,7 +23,6 @@ var common = require('../common'); var assert = require('assert'); var events = require('events'); - var count = 0; function listener1() { @@ -41,21 +40,45 @@ function listener3() { count++; } +function remove1() { + assert(0); +} + +function remove2() { + assert(0); +} + var e1 = new events.EventEmitter(); e1.on('hello', listener1); +e1.on('removeListener', common.mustCall(function(name, cb) { + assert.equal(name, 'hello'); + assert.equal(cb, listener1); +})); e1.removeListener('hello', listener1); assert.deepEqual([], e1.listeners('hello')); var e2 = new events.EventEmitter(); e2.on('hello', listener1); +e2.on('removeListener', assert.fail); e2.removeListener('hello', listener2); assert.deepEqual([listener1], e2.listeners('hello')); var e3 = new events.EventEmitter(); e3.on('hello', listener1); e3.on('hello', listener2); +e3.on('removeListener', common.mustCall(function(name, cb) { + assert.equal(name, 'hello'); + assert.equal(cb, listener1); +})); e3.removeListener('hello', listener1); assert.deepEqual([listener2], e3.listeners('hello')); - - +var e4 = new events.EventEmitter(); +e4.on('removeListener', common.mustCall(function(name, cb) { + if (cb !== remove1) return; + this.removeListener('quux', remove2); + this.emit('quux'); +}, 2)); +e4.on('quux', remove1); +e4.on('quux', remove2); +e4.removeListener('quux', remove1);