Skip to content

Commit 7008719

Browse files
apapirovskiMylesBorins
authored andcommitted
events: remove reaches into _events internals
Refactor lib & src code to eliminate all deep reaches into the internal _events dictionary object, instead use available APIs and add an extra method to EventEmitter: rawListeners. PR-URL: #17440 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Timothy Gu <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent 97eaaf9 commit 7008719

File tree

7 files changed

+69
-22
lines changed

7 files changed

+69
-22
lines changed

doc/api/events.md

+33
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,39 @@ to indicate an unlimited number of listeners.
574574

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

577+
### emitter.rawListeners(eventName)
578+
<!-- YAML
579+
added: REPLACEME
580+
-->
581+
- `eventName` {any}
582+
583+
Returns a copy of the array of listeners for the event named `eventName`,
584+
including any wrappers (such as those created by `.once`).
585+
586+
```js
587+
const emitter = new EventEmitter();
588+
emitter.once('log', () => console.log('log once'));
589+
590+
// Returns a new Array with a function `onceWrapper` which has a property
591+
// `listener` which contains the original listener bound above
592+
const listeners = emitter.rawListeners('log');
593+
const logFnWrapper = listeners[0];
594+
595+
// logs "log once" to the console and does not unbind the `once` event
596+
logFnWrapper.listener();
597+
598+
// logs "log once" to the console and removes the listener
599+
logFnWrapper();
600+
601+
emitter.on('log', () => console.log('log persistently'));
602+
// will return a new Array with a single function bound by `on` above
603+
const newListeners = emitter.rawListeners('log');
604+
605+
// logs "log persistently" twice
606+
newListeners[0]();
607+
emitter.emit('log');
608+
```
609+
577610
[`--trace-warnings`]: cli.html#cli_trace_warnings
578611
[`EventEmitter.defaultMaxListeners`]: #events_eventemitter_defaultmaxlisteners
579612
[`domain`]: domain.html

lib/events.js

+14-4
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ EventEmitter.usingDomains = false;
3636

3737
EventEmitter.prototype.domain = undefined;
3838
EventEmitter.prototype._events = undefined;
39+
EventEmitter.prototype._eventsCount = 0;
3940
EventEmitter.prototype._maxListeners = undefined;
4041

4142
// By default EventEmitters will print a warning if more than 10 listeners are
@@ -393,8 +394,8 @@ EventEmitter.prototype.removeAllListeners =
393394
return this;
394395
};
395396

396-
EventEmitter.prototype.listeners = function listeners(type) {
397-
const events = this._events;
397+
function _listeners(target, type, unwrap) {
398+
const events = target._events;
398399

399400
if (events === undefined)
400401
return [];
@@ -404,9 +405,18 @@ EventEmitter.prototype.listeners = function listeners(type) {
404405
return [];
405406

406407
if (typeof evlistener === 'function')
407-
return [evlistener.listener || evlistener];
408+
return unwrap ? [evlistener.listener || evlistener] : [evlistener];
409+
410+
return unwrap ?
411+
unwrapListeners(evlistener) : arrayClone(evlistener, evlistener.length);
412+
}
413+
414+
EventEmitter.prototype.listeners = function listeners(type) {
415+
return _listeners(this, type, true);
416+
};
408417

409-
return unwrapListeners(evlistener);
418+
EventEmitter.prototype.rawListeners = function rawListeners(type) {
419+
return _listeners(this, type, false);
410420
};
411421

412422
EventEmitter.listenerCount = function(emitter, type) {

lib/internal/bootstrap_node.js

-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414
function startup() {
1515
const EventEmitter = NativeModule.require('events');
16-
process._eventsCount = 0;
1716

1817
const origProcProto = Object.getPrototypeOf(process);
1918
Object.setPrototypeOf(origProcProto, EventEmitter.prototype);

lib/vm.js

+3-10
Original file line numberDiff line numberDiff line change
@@ -57,15 +57,15 @@ const realRunInThisContext = Script.prototype.runInThisContext;
5757
const realRunInContext = Script.prototype.runInContext;
5858

5959
Script.prototype.runInThisContext = function(options) {
60-
if (options && options.breakOnSigint && process._events.SIGINT) {
60+
if (options && options.breakOnSigint && process.listenerCount('SIGINT') > 0) {
6161
return sigintHandlersWrap(realRunInThisContext, this, [options]);
6262
} else {
6363
return realRunInThisContext.call(this, options);
6464
}
6565
};
6666

6767
Script.prototype.runInContext = function(contextifiedSandbox, options) {
68-
if (options && options.breakOnSigint && process._events.SIGINT) {
68+
if (options && options.breakOnSigint && process.listenerCount('SIGINT') > 0) {
6969
return sigintHandlersWrap(realRunInContext, this,
7070
[contextifiedSandbox, options]);
7171
} else {
@@ -96,14 +96,7 @@ function createScript(code, options) {
9696
// Remove all SIGINT listeners and re-attach them after the wrapped function
9797
// has executed, so that caught SIGINT are handled by the listeners again.
9898
function sigintHandlersWrap(fn, thisArg, argsArray) {
99-
// Using the internal list here to make sure `.once()` wrappers are used,
100-
// not the original ones.
101-
let sigintListeners = process._events.SIGINT;
102-
103-
if (Array.isArray(sigintListeners))
104-
sigintListeners = sigintListeners.slice();
105-
else
106-
sigintListeners = [sigintListeners];
99+
const sigintListeners = process.rawListeners('SIGINT');
107100

108101
process.removeAllListeners('SIGINT');
109102

src/env.h

-1
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,6 @@ class ModuleWrap;
145145
V(env_pairs_string, "envPairs") \
146146
V(errno_string, "errno") \
147147
V(error_string, "error") \
148-
V(events_string, "_events") \
149148
V(exiting_string, "_exiting") \
150149
V(exit_code_string, "exitCode") \
151150
V(exit_string, "exit") \

src/node.cc

-6
Original file line numberDiff line numberDiff line change
@@ -3684,12 +3684,6 @@ void SetupProcessObject(Environment* env,
36843684
env->SetMethod(process, "_setupNextTick", SetupNextTick);
36853685
env->SetMethod(process, "_setupPromises", SetupPromises);
36863686
env->SetMethod(process, "_setupDomainUse", SetupDomainUse);
3687-
3688-
// pre-set _events object for faster emit checks
3689-
Local<Object> events_obj = Object::New(env->isolate());
3690-
CHECK(events_obj->SetPrototype(env->context(),
3691-
Null(env->isolate())).FromJust());
3692-
process->Set(env->events_string(), events_obj);
36933687
}
36943688

36953689

test/parallel/test-event-emitter-listeners.js

+19
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,22 @@ function listener2() {}
8282
const s = new TestStream();
8383
assert.deepStrictEqual(s.listeners('foo'), []);
8484
}
85+
86+
{
87+
const ee = new events.EventEmitter();
88+
ee.on('foo', listener);
89+
const wrappedListener = ee.rawListeners('foo');
90+
assert.strictEqual(wrappedListener.length, 1);
91+
assert.strictEqual(wrappedListener[0], listener);
92+
assert.notStrictEqual(wrappedListener, ee.rawListeners('foo'));
93+
ee.once('foo', listener);
94+
const wrappedListeners = ee.rawListeners('foo');
95+
assert.strictEqual(wrappedListeners.length, 2);
96+
assert.strictEqual(wrappedListeners[0], listener);
97+
assert.notStrictEqual(wrappedListeners[1], listener);
98+
assert.strictEqual(wrappedListeners[1].listener, listener);
99+
assert.notStrictEqual(wrappedListeners, ee.rawListeners('foo'));
100+
ee.emit('foo');
101+
assert.strictEqual(wrappedListeners.length, 2);
102+
assert.strictEqual(wrappedListeners[1].listener, listener);
103+
}

0 commit comments

Comments
 (0)