summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames M Snell <jasnell@gmail.com>2016-04-03 19:55:22 -0700
committerJames M Snell <jasnell@gmail.com>2016-04-22 09:28:37 -0700
commit0e7d57af3573b4dcba81217bba2f041dbdc173dc (patch)
treec6c541c4e5d8f7d910d97ba0316de0be9338ea69
parentf85412d49b0ac1b3d2fa1e9c0dfe2491ea3aa9be (diff)
downloadandroid-node-v8-0e7d57af3573b4dcba81217bba2f041dbdc173dc.tar.gz
android-node-v8-0e7d57af3573b4dcba81217bba2f041dbdc173dc.tar.bz2
android-node-v8-0e7d57af3573b4dcba81217bba2f041dbdc173dc.zip
events: add prependListener() and prependOnceListener()
A handful of modules (including readable-streams) make inappropriate use of the internal _events property. One such use is to prepend an event listener to the front of the array of listeners. This adds EE.prototype.prependListener() and EE.prototype.prependOnceListener() methods to add handlers to the *front* of the listener array. Doc update and test case is included. Fixes: https://github.com/nodejs/node/issues/1817 PR-URL: https://github.com/nodejs/node/pull/6032 Reviewed-By: Сковорода Никита Андреевич <chalkerx@gmail.com> Reviewed-By: Brian White <mscdex@mscdex.net>
-rw-r--r--doc/api/events.md70
-rw-r--r--lib/_stream_readable.js29
-rw-r--r--lib/events.js63
-rw-r--r--test/parallel/test-event-emitter-prepend.js41
4 files changed, 174 insertions, 29 deletions
diff --git a/doc/api/events.md b/doc/api/events.md
index 393a69ee02..501a94b79d 100644
--- a/doc/api/events.md
+++ b/doc/api/events.md
@@ -340,6 +340,9 @@ console.log(util.inspect(server.listeners('connection')));
### emitter.on(eventName, listener)
+* `eventName` {string|Symbol} The name of the event.
+* `listener` {Function} The callback function
+
Adds the `listener` function to the end of the listeners array for the
event named `eventName`. No checks are made to see if the `listener` has
already been added. Multiple calls passing the same combination of `eventName`
@@ -354,8 +357,25 @@ server.on('connection', (stream) => {
Returns a reference to the `EventEmitter` so calls can be chained.
+By default, event listeners are invoked in the order they are added. The
+`emitter.prependListener()` method can be used as an alternative to add the
+event listener to the beginning of the listeners array.
+
+```js
+const myEE = new EventEmitter();
+myEE.on('foo', () => console.log('a'));
+myEE.prependListener('foo', () => console.log('b'));
+myEE.emit('foo');
+ // Prints:
+ // b
+ // a
+```
+
### emitter.once(eventName, listener)
+* `eventName` {string|Symbol} The name of the event.
+* `listener` {Function} The callback function
+
Adds a **one time** `listener` function for the event named `eventName`. This
listener is invoked only the next time `eventName` is triggered, after which
it is removed.
@@ -368,6 +388,56 @@ server.once('connection', (stream) => {
Returns a reference to the `EventEmitter` so calls can be chained.
+By default, event listeners are invoked in the order they are added. The
+`emitter.prependOnceListener()` method can be used as an alternative to add the
+event listener to the beginning of the listeners array.
+
+```js
+const myEE = new EventEmitter();
+myEE.once('foo', () => console.log('a'));
+myEE.prependOnceListener('foo', () => console.log('b'));
+myEE.emit('foo');
+ // Prints:
+ // b
+ // a
+```
+
+### emitter.prependListener(eventName, listener)
+
+* `eventName` {string|Symbol} The name of the event.
+* `listener` {Function} The callback function
+
+Adds the `listener` function to the *beginning* of the listeners array for the
+event named `eventName`. No checks are made to see if the `listener` has
+already been added. Multiple calls passing the same combination of `eventName`
+and `listener` will result in the `listener` being added, and called, multiple
+times.
+
+```js
+server.prependListener('connection', (stream) => {
+ console.log('someone connected!');
+});
+```
+
+Returns a reference to the `EventEmitter` so calls can be chained.
+
+### emitter.prependOnceListener(eventName, listener)
+
+* `eventName` {string|Symbol} The name of the event.
+* `listener` {Function} The callback function
+
+Adds a **one time** `listener` function for the event named `eventName` to the
+*beginning* of the listeners array. This listener is invoked only the next time
+`eventName` is triggered, after which it is removed.
+
+```js
+server.prependOnceListener('connection', (stream) => {
+ console.log('Ah, we have our first user!');
+});
+```
+
+Returns a reference to the `EventEmitter` so calls can be chained.
+
### emitter.removeAllListeners([eventName])
Removes all listeners, or those of the specified `eventName`.
diff --git a/lib/_stream_readable.js b/lib/_stream_readable.js
index 275691cbb2..afe3b3baf0 100644
--- a/lib/_stream_readable.js
+++ b/lib/_stream_readable.js
@@ -12,6 +12,25 @@ var StringDecoder;
util.inherits(Readable, Stream);
+const hasPrependListener = typeof EE.prototype.prependListener === 'function';
+
+function prependListener(emitter, event, fn) {
+ if (hasPrependListener)
+ return emitter.prependListener(event, fn);
+
+ // This is a brutally ugly hack to make sure that our error handler
+ // is attached before any userland ones. NEVER DO THIS. This is here
+ // only because this code needs to continue to work with older versions
+ // of Node.js that do not include the prependListener() method. The goal
+ // is to eventually remove this hack.
+ if (!emitter._events || !emitter._events[event])
+ emitter.on(event, fn);
+ else if (Array.isArray(emitter._events[event]))
+ emitter._events[event].unshift(fn);
+ else
+ emitter._events[event] = [fn, emitter._events[event]];
+}
+
function ReadableState(options, stream) {
options = options || {};
@@ -558,15 +577,9 @@ Readable.prototype.pipe = function(dest, pipeOpts) {
if (EE.listenerCount(dest, 'error') === 0)
dest.emit('error', er);
}
- // This is a brutally ugly hack to make sure that our error handler
- // is attached before any userland ones. NEVER DO THIS.
- if (!dest._events || !dest._events.error)
- dest.on('error', onerror);
- else if (Array.isArray(dest._events.error))
- dest._events.error.unshift(onerror);
- else
- dest._events.error = [onerror, dest._events.error];
+ // Make sure our error handler is attached before userland ones.
+ prependListener(dest, 'error', onerror);
// Both close and finish should trigger unpipe, but only once.
function onclose() {
diff --git a/lib/events.js b/lib/events.js
index 36eb7835e4..04d13d4386 100644
--- a/lib/events.js
+++ b/lib/events.js
@@ -207,7 +207,7 @@ EventEmitter.prototype.emit = function emit(type) {
return true;
};
-EventEmitter.prototype.addListener = function addListener(type, listener) {
+function _addListener(target, type, listener, prepend) {
var m;
var events;
var existing;
@@ -215,20 +215,20 @@ EventEmitter.prototype.addListener = function addListener(type, listener) {
if (typeof listener !== 'function')
throw new TypeError('"listener" argument must be a function');
- events = this._events;
+ events = target._events;
if (!events) {
- events = this._events = new EventHandlers();
- this._eventsCount = 0;
+ events = target._events = new EventHandlers();
+ target._eventsCount = 0;
} else {
// To avoid recursion in the case that type === "newListener"! Before
// adding it to the listeners, first emit "newListener".
if (events.newListener) {
- this.emit('newListener', type,
- listener.listener ? listener.listener : listener);
+ target.emit('newListener', type,
+ 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
- events = this._events;
+ events = target._events;
}
existing = events[type];
}
@@ -236,19 +236,24 @@ EventEmitter.prototype.addListener = function addListener(type, listener) {
if (!existing) {
// Optimize the case of one listener. Don't need the extra array object.
existing = events[type] = listener;
- ++this._eventsCount;
+ ++target._eventsCount;
} else {
if (typeof existing === 'function') {
// Adding the second element, need to change to array.
- existing = events[type] = [existing, listener];
+ existing = events[type] = prepend ? [listener, existing] :
+ [existing, listener];
} else {
// If we've already got an array, just append.
- existing.push(listener);
+ if (prepend) {
+ existing.unshift(listener);
+ } else {
+ existing.push(listener);
+ }
}
// Check for listener leak
if (!existing.warned) {
- m = $getMaxListeners(this);
+ m = $getMaxListeners(target);
if (m && m > 0 && existing.length > m) {
existing.warned = true;
process.emitWarning('Possible EventEmitter memory leak detected. ' +
@@ -258,32 +263,48 @@ EventEmitter.prototype.addListener = function addListener(type, listener) {
}
}
- return this;
+ return target;
+}
+
+EventEmitter.prototype.addListener = function addListener(type, listener) {
+ return _addListener(this, type, listener, false);
};
EventEmitter.prototype.on = EventEmitter.prototype.addListener;
-EventEmitter.prototype.once = function once(type, listener) {
- if (typeof listener !== 'function')
- throw new TypeError('"listener" argument must be a function');
+EventEmitter.prototype.prependListener =
+ function prependListener(type, listener) {
+ return _addListener(this, type, listener, true);
+ };
+function _onceWrap(target, type, listener) {
var fired = false;
-
function g() {
- this.removeListener(type, g);
-
+ target.removeListener(type, g);
if (!fired) {
fired = true;
- listener.apply(this, arguments);
+ listener.apply(target, arguments);
}
}
-
g.listener = listener;
- this.on(type, g);
+ return g;
+}
+EventEmitter.prototype.once = function once(type, listener) {
+ if (typeof listener !== 'function')
+ throw new TypeError('"listener" argument must be a function');
+ this.on(type, _onceWrap(this, type, listener));
return this;
};
+EventEmitter.prototype.prependOnceListener =
+ function prependOnceListener(type, listener) {
+ if (typeof listener !== 'function')
+ throw new TypeError('"listener" argument must be a function');
+ this.prependListener(type, _onceWrap(this, type, listener));
+ return this;
+ };
+
// emits a 'removeListener' event iff the listener was removed
EventEmitter.prototype.removeListener =
function removeListener(type, listener) {
diff --git a/test/parallel/test-event-emitter-prepend.js b/test/parallel/test-event-emitter-prepend.js
new file mode 100644
index 0000000000..6be63a2616
--- /dev/null
+++ b/test/parallel/test-event-emitter-prepend.js
@@ -0,0 +1,41 @@
+'use strict';
+
+const common = require('../common');
+const EventEmitter = require('events');
+const assert = require('assert');
+
+const myEE = new EventEmitter();
+var m = 0;
+// This one comes last.
+myEE.on('foo', common.mustCall(() => assert.equal(m, 2)));
+
+// This one comes second.
+myEE.prependListener('foo', common.mustCall(() => assert.equal(m++, 1)));
+
+// This one comes first.
+myEE.prependOnceListener('foo', common.mustCall(() => assert.equal(m++, 0)));
+
+myEE.emit('foo');
+
+
+// Test fallback if prependListener is undefined.
+const stream = require('stream');
+const util = require('util');
+
+delete EventEmitter.prototype.prependListener;
+
+function Writable() {
+ this.writable = true;
+ stream.Stream.call(this);
+}
+util.inherits(Writable, stream.Stream);
+
+function Readable() {
+ this.readable = true;
+ stream.Stream.call(this);
+}
+util.inherits(Readable, stream.Stream);
+
+const w = new Writable();
+const r = new Readable();
+r.pipe(w);