Skip to content

Commit 209ed51

Browse files
committed
events: add ability to prepend event listeners
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. To address part of the issue, this adds a new optional bitwise flag to the addListener/on/once methods that, when set, causes the listener to be prepended. Doc update and test case is included. Fixes: nodejs#1817
1 parent 7c9a691 commit 209ed51

File tree

5 files changed

+90
-21
lines changed

5 files changed

+90
-21
lines changed

doc/api/events.markdown

+42-4
Original file line numberDiff line numberDiff line change
@@ -284,9 +284,9 @@ emitter.once('event', () => {
284284
});
285285
```
286286

287-
### emitter.addListener(eventName, listener)
287+
### emitter.addListener(eventName, listener[, flags])
288288

289-
Alias for `emitter.on(eventName, listener)`.
289+
Alias for `emitter.on(eventName, listener[, flags])`.
290290

291291
### emitter.emit(eventName[, arg1][, arg2][, ...])
292292

@@ -338,7 +338,12 @@ console.log(util.inspect(server.listeners('connection')));
338338
// Prints: [ [Function] ]
339339
```
340340

341-
### emitter.on(eventName, listener)
341+
### emitter.on(eventName, listener[, flags])
342+
343+
* `eventName` {string|Symbol} The name of the event.
344+
* `listener` {Function} The callback function
345+
* `flags` {Bitwise field} Optional flags
346+
* `EventEmitter.F_PREPEND` = `0x1`
342347

343348
Adds the `listener` function to the end of the listeners array for the
344349
event named `eventName`. No checks are made to see if the `listener` has
@@ -354,7 +359,26 @@ server.on('connection', (stream) => {
354359

355360
Returns a reference to the `EventEmitter` so calls can be chained.
356361

357-
### emitter.once(eventName, listener)
362+
By default, event listeners are invoked in the order they are added. The
363+
`EventEmitter.F_PREPEND` flag can be used to add the event listener to the
364+
beginning of the list of registered listeners.
365+
366+
```js
367+
const myEE = new EventEmitter();
368+
myEE.on('foo', () => console.log('a'));
369+
myEE.on('foo', () => {console.log('b')}, EventEmitter.F_PREPEND);
370+
myEE.emit('foo');
371+
// Prints:
372+
// b
373+
// a
374+
```
375+
376+
### emitter.once(eventName, listener, flags)
377+
378+
* `eventName` {string|Symbol} The name of the event.
379+
* `listener` {Function} The callback function
380+
* `flags` {Bitwise field} Optional flags
381+
* `EventEmitter.F_PREPEND` = `0x1`
358382

359383
Adds a **one time** `listener` function for the event named `eventName`. This
360384
listener is invoked only the next time `eventName` is triggered, after which
@@ -368,6 +392,20 @@ server.once('connection', (stream) => {
368392

369393
Returns a reference to the `EventEmitter` so calls can be chained.
370394

395+
By default, event listeners are invoked in the order they are added. The
396+
`EventEmitter.F_PREPEND` flag can be used to add the event listener to the
397+
beginning of the list of registered listeners.
398+
399+
```js
400+
const myEE = new EventEmitter();
401+
myEE.once('foo', () => console.log('a'));
402+
myEE.once('foo', () => {console.log('b')}, EventEmitter.F_PREPEND);
403+
myEE.emit('foo');
404+
// Prints:
405+
// b
406+
// a
407+
```
408+
371409
### emitter.removeAllListeners([eventName])
372410

373411
Removes all listeners, or those of the specified `eventName`.

lib/_http_server.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -577,8 +577,8 @@ function unconsume(parser, socket) {
577577
}
578578
}
579579

580-
function socketOnWrap(ev, fn) {
581-
var res = net.Socket.prototype.on.call(this, ev, fn);
580+
function socketOnWrap(ev, fn, flags) {
581+
var res = net.Socket.prototype.on.call(this, ev, fn, flags);
582582
if (!this.parser) {
583583
this.on = net.Socket.prototype.on;
584584
return res;

lib/_stream_readable.js

+4-10
Original file line numberDiff line numberDiff line change
@@ -558,15 +558,9 @@ Readable.prototype.pipe = function(dest, pipeOpts) {
558558
if (EE.listenerCount(dest, 'error') === 0)
559559
dest.emit('error', er);
560560
}
561-
// This is a brutally ugly hack to make sure that our error handler
562-
// is attached before any userland ones. NEVER DO THIS.
563-
if (!dest._events || !dest._events.error)
564-
dest.on('error', onerror);
565-
else if (Array.isArray(dest._events.error))
566-
dest._events.error.unshift(onerror);
567-
else
568-
dest._events.error = [onerror, dest._events.error];
569561

562+
// Make sure our error handler is attached before userland ones.
563+
dest.on('error', onerror, EE.F_PREPEND);
570564

571565
// Both close and finish should trigger unpipe, but only once.
572566
function onclose() {
@@ -669,8 +663,8 @@ Readable.prototype.unpipe = function(dest) {
669663

670664
// set up data events if they are asked for
671665
// Ensure readable listeners eventually get something
672-
Readable.prototype.on = function(ev, fn) {
673-
var res = Stream.prototype.on.call(this, ev, fn);
666+
Readable.prototype.on = function(ev, fn, flags) {
667+
var res = Stream.prototype.on.call(this, ev, fn, flags);
674668

675669
// If listening to data, and it has not explicitly been paused,
676670
// then call resume to start the flow of data on the next tick.

lib/events.js

+21-5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ function EventEmitter() {
77
}
88
module.exports = EventEmitter;
99

10+
Object.defineProperty(EventEmitter, 'F_PREPEND', {
11+
configurable: false,
12+
enumerable: true,
13+
value: 0x1
14+
});
15+
1016
// Backwards-compat with node 0.10.x
1117
EventEmitter.EventEmitter = EventEmitter;
1218

@@ -201,11 +207,16 @@ EventEmitter.prototype.emit = function emit(type) {
201207
return true;
202208
};
203209

204-
EventEmitter.prototype.addListener = function addListener(type, listener) {
210+
EventEmitter.prototype.addListener = function addListener(type,
211+
listener,
212+
flags) {
205213
var m;
206214
var events;
207215
var existing;
208216

217+
flags >>>= 0;
218+
const prepend = (flags & EventEmitter.F_PREPEND) === EventEmitter.F_PREPEND;
219+
209220
if (typeof listener !== 'function')
210221
throw new TypeError('"listener" argument must be a function');
211222

@@ -234,10 +245,15 @@ EventEmitter.prototype.addListener = function addListener(type, listener) {
234245
} else {
235246
if (typeof existing === 'function') {
236247
// Adding the second element, need to change to array.
237-
existing = events[type] = [existing, listener];
248+
existing = events[type] = prepend ? [listener, existing] :
249+
[existing, listener];
238250
} else {
239251
// If we've already got an array, just append.
240-
existing.push(listener);
252+
if (prepend) {
253+
existing.unshift(listener);
254+
} else {
255+
existing.push(listener);
256+
}
241257
}
242258

243259
// Check for listener leak
@@ -257,7 +273,7 @@ EventEmitter.prototype.addListener = function addListener(type, listener) {
257273

258274
EventEmitter.prototype.on = EventEmitter.prototype.addListener;
259275

260-
EventEmitter.prototype.once = function once(type, listener) {
276+
EventEmitter.prototype.once = function once(type, listener, flags) {
261277
if (typeof listener !== 'function')
262278
throw new TypeError('"listener" argument must be a function');
263279

@@ -273,7 +289,7 @@ EventEmitter.prototype.once = function once(type, listener) {
273289
}
274290

275291
g.listener = listener;
276-
this.on(type, g);
292+
this.on(type, g, flags);
277293

278294
return this;
279295
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const EventEmitter = require('events');
5+
const assert = require('assert');
6+
7+
const myEE = new EventEmitter();
8+
var m = 0;
9+
// This one comes last.
10+
myEE.on('foo', common.mustCall(() => {
11+
assert.equal(m, 2);
12+
}));
13+
// This one comes second.
14+
myEE.on('foo', () => {
15+
assert.equal(m++, 1);
16+
}, EventEmitter.F_PREPEND);
17+
// This one comes first.
18+
myEE.once('foo', () => {
19+
assert.equal(m++, 0);
20+
}, EventEmitter.F_PREPEND);
21+
myEE.emit('foo');

0 commit comments

Comments
 (0)