Skip to content

Commit 9c25002

Browse files
authored
events: repurpose events.listenerCount() to accept EventTargets
PR-URL: nodejs#60214 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Benjamin Gruenbaum <[email protected]>
1 parent 4451309 commit 9c25002

22 files changed

+159
-124
lines changed

doc/api/deprecations.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -786,6 +786,9 @@ The [`domain`][] module is deprecated and should not be used.
786786

787787
<!-- YAML
788788
changes:
789+
- version: REPLACEME
790+
pr-url: https://github.com/nodejs/node/pull/60214
791+
description: Deprecation revoked.
789792
- version:
790793
- v6.12.0
791794
- v4.8.6
@@ -796,10 +799,12 @@ changes:
796799
description: Documentation-only deprecation.
797800
-->
798801

799-
Type: Documentation-only
802+
Type: Revoked
800803

801-
The [`events.listenerCount(emitter, eventName)`][] API is
802-
deprecated. Please use [`emitter.listenerCount(eventName)`][] instead.
804+
The [`events.listenerCount(emitter, eventName)`][] API was deprecated, as it
805+
provided identical fuctionality to [`emitter.listenerCount(eventName)`][]. The
806+
deprecation was revoked because this function has been repurposed to also
807+
accept {EventTarget} arguments.
803808

804809
### DEP0034: `fs.exists(path, callback)`
805810

@@ -4428,7 +4433,7 @@ import { opendir } from 'node:fs/promises';
44284433
[`domain`]: domain.md
44294434
[`ecdh.setPublicKey()`]: crypto.md#ecdhsetpublickeypublickey-encoding
44304435
[`emitter.listenerCount(eventName)`]: events.md#emitterlistenercounteventname-listener
4431-
[`events.listenerCount(emitter, eventName)`]: events.md#eventslistenercountemitter-eventname
4436+
[`events.listenerCount(emitter, eventName)`]: events.md#eventslistenercountemitterortarget-eventname
44324437
[`fs.Dir`]: fs.md#class-fsdir
44334438
[`fs.FileHandle`]: fs.md#class-filehandle
44344439
[`fs.access()`]: fs.md#fsaccesspath-mode-callback

doc/api/events.md

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1622,39 +1622,66 @@ changes:
16221622

16231623
See how to write a custom [rejection handler][rejection].
16241624

1625-
## `events.listenerCount(emitter, eventName)`
1625+
## `events.listenerCount(emitterOrTarget, eventName)`
16261626

16271627
<!-- YAML
16281628
added: v0.9.12
1629-
deprecated: v3.2.0
1629+
changes:
1630+
- version: REPLACEME
1631+
pr-url: https://github.com/nodejs/node/pull/60214
1632+
description: Now accepts EventTarget arguments.
1633+
- version: REPLACEME
1634+
pr-url: https://github.com/nodejs/node/pull/60214
1635+
description: Deprecation revoked.
1636+
- version: v3.2.0
1637+
pr-url: https://github.com/nodejs/node/pull/2349
1638+
description: Documentation-only deprecation.
16301639
-->
16311640

1632-
> Stability: 0 - Deprecated: Use [`emitter.listenerCount()`][] instead.
1641+
* `emitterOrTarget` {EventEmitter|EventTarget}
1642+
* `eventName` {string|symbol}
1643+
* Returns: {integer}
1644+
1645+
Returns the number of registered listeners for the event named `eventName`.
16331646

1634-
* `emitter` {EventEmitter} The emitter to query
1635-
* `eventName` {string|symbol} The event name
1647+
For `EventEmitter`s this behaves exactly the same as calling `.listenerCount`
1648+
on the emitter.
16361649

1637-
A class method that returns the number of listeners for the given `eventName`
1638-
registered on the given `emitter`.
1650+
For `EventTarget`s this is the only way to obtain the listener count. This can
1651+
be useful for debugging and diagnostic purposes.
16391652

16401653
```mjs
16411654
import { EventEmitter, listenerCount } from 'node:events';
16421655

1643-
const myEmitter = new EventEmitter();
1644-
myEmitter.on('event', () => {});
1645-
myEmitter.on('event', () => {});
1646-
console.log(listenerCount(myEmitter, 'event'));
1647-
// Prints: 2
1656+
{
1657+
const ee = new EventEmitter();
1658+
ee.on('event', () => {});
1659+
ee.on('event', () => {});
1660+
console.log(listenerCount(ee, 'event')); // 2
1661+
}
1662+
{
1663+
const et = new EventTarget();
1664+
et.addEventListener('event', () => {});
1665+
et.addEventListener('event', () => {});
1666+
console.log(listenerCount(et, 'event')); // 2
1667+
}
16481668
```
16491669

16501670
```cjs
16511671
const { EventEmitter, listenerCount } = require('node:events');
16521672

1653-
const myEmitter = new EventEmitter();
1654-
myEmitter.on('event', () => {});
1655-
myEmitter.on('event', () => {});
1656-
console.log(listenerCount(myEmitter, 'event'));
1657-
// Prints: 2
1673+
{
1674+
const ee = new EventEmitter();
1675+
ee.on('event', () => {});
1676+
ee.on('event', () => {});
1677+
console.log(listenerCount(ee, 'event')); // 2
1678+
}
1679+
{
1680+
const et = new EventTarget();
1681+
et.addEventListener('event', () => {});
1682+
et.addEventListener('event', () => {});
1683+
console.log(listenerCount(et, 'event')); // 2
1684+
}
16581685
```
16591686

16601687
## `events.on(emitter, eventName[, options])`
@@ -2648,7 +2675,6 @@ to the `EventTarget`.
26482675
[`Event` Web API]: https://dom.spec.whatwg.org/#event
26492676
[`domain`]: domain.md
26502677
[`e.stopImmediatePropagation()`]: #eventstopimmediatepropagation
2651-
[`emitter.listenerCount()`]: #emitterlistenercounteventname-listener
26522678
[`emitter.removeListener()`]: #emitterremovelistenereventname-listener
26532679
[`emitter.setMaxListeners(n)`]: #emittersetmaxlistenersn
26542680
[`event.defaultPrevented`]: #eventdefaultprevented

lib/events.js

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ const {
3333
Error,
3434
ErrorCaptureStackTrace,
3535
FunctionPrototypeBind,
36-
FunctionPrototypeCall,
3736
NumberMAX_SAFE_INTEGER,
3837
ObjectDefineProperties,
3938
ObjectDefineProperty,
@@ -215,6 +214,7 @@ module.exports.once = once;
215214
module.exports.on = on;
216215
module.exports.getEventListeners = getEventListeners;
217216
module.exports.getMaxListeners = getMaxListeners;
217+
module.exports.listenerCount = listenerCount;
218218
// Backwards-compat with node 0.10.x
219219
EventEmitter.EventEmitter = EventEmitter;
220220

@@ -813,31 +813,14 @@ EventEmitter.prototype.rawListeners = function rawListeners(type) {
813813
return _listeners(this, type, false);
814814
};
815815

816-
/**
817-
* Returns the number of listeners listening to the event name
818-
* specified as `type`.
819-
* @deprecated since v3.2.0
820-
* @param {EventEmitter} emitter
821-
* @param {string | symbol} type
822-
* @returns {number}
823-
*/
824-
EventEmitter.listenerCount = function(emitter, type) {
825-
if (typeof emitter.listenerCount === 'function') {
826-
return emitter.listenerCount(type);
827-
}
828-
return FunctionPrototypeCall(listenerCount, emitter, type);
829-
};
830-
831-
EventEmitter.prototype.listenerCount = listenerCount;
832-
833816
/**
834817
* Returns the number of listeners listening to event name
835818
* specified as `type`.
836819
* @param {string | symbol} type
837-
* @param {Function} listener
820+
* @param {Function} [listener]
838821
* @returns {number}
839822
*/
840-
function listenerCount(type, listener) {
823+
EventEmitter.prototype.listenerCount = function listenerCount(type, listener) {
841824
const events = this._events;
842825

843826
if (events !== undefined) {
@@ -867,7 +850,7 @@ function listenerCount(type, listener) {
867850
}
868851

869852
return 0;
870-
}
853+
};
871854

872855
/**
873856
* Returns an array listing the events for which
@@ -949,6 +932,25 @@ function getMaxListeners(emitterOrTarget) {
949932
emitterOrTarget);
950933
}
951934

935+
/**
936+
* Returns the number of registered listeners for `type`.
937+
* @param {EventEmitter | EventTarget} emitterOrTarget
938+
* @param {string | symbol} type
939+
* @returns {number}
940+
*/
941+
function listenerCount(emitterOrTarget, type) {
942+
if (typeof emitterOrTarget.listenerCount === 'function') {
943+
return emitterOrTarget.listenerCount(type);
944+
}
945+
const { isEventTarget, kEvents } = require('internal/event_target');
946+
if (isEventTarget(emitterOrTarget)) {
947+
return emitterOrTarget[kEvents].get(type)?.size ?? 0;
948+
}
949+
throw new ERR_INVALID_ARG_TYPE('emitter',
950+
['EventEmitter', 'EventTarget'],
951+
emitterOrTarget);
952+
}
953+
952954
/**
953955
* Creates a `Promise` that is fulfilled when the emitter
954956
* emits the given event.

lib/internal/streams/legacy.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ Stream.prototype.pipe = function(dest, options) {
5959
// Don't leave dangling pipes when there are errors.
6060
function onerror(er) {
6161
cleanup();
62-
if (EE.listenerCount(this, 'error') === 0) {
62+
// If we removed the last error handler, trigger an unhandled error event.
63+
if (this.listenerCount?.('error') === 0) {
6364
this.emit('error', er);
6465
}
6566
}

test/parallel/test-aborted-util.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
const common = require('../common');
55
const { aborted } = require('util');
66
const assert = require('assert');
7-
const { getEventListeners } = require('events');
7+
const { listenerCount } = require('events');
88
const { inspect } = require('util');
99

1010
const {
@@ -17,7 +17,7 @@ test('Aborted works when provided a resource', async () => {
1717
ac.abort();
1818
await promise;
1919
assert.strictEqual(ac.signal.aborted, true);
20-
assert.strictEqual(getEventListeners(ac.signal, 'abort').length, 0);
20+
assert.strictEqual(listenerCount(ac.signal, 'abort'), 0);
2121
});
2222

2323
test('Aborted with gc cleanup', async () => {
@@ -31,7 +31,7 @@ test('Aborted with gc cleanup', async () => {
3131
globalThis.gc();
3232
ac.abort();
3333
assert.strictEqual(ac.signal.aborted, true);
34-
assert.strictEqual(getEventListeners(ac.signal, 'abort').length, 0);
34+
assert.strictEqual(listenerCount(ac.signal, 'abort'), 0);
3535
resolve();
3636
}));
3737

test/parallel/test-child-process-execfile.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
const common = require('../common');
44
const assert = require('assert');
55
const { execFile, execFileSync } = require('child_process');
6-
const { getEventListeners } = require('events');
6+
const { listenerCount } = require('events');
77
const { getSystemErrorName } = require('util');
88
const fixtures = require('../common/fixtures');
99
const os = require('os');
@@ -106,7 +106,7 @@ common.expectWarning(
106106
const { signal } = ac;
107107

108108
const callback = common.mustCall((err) => {
109-
assert.strictEqual(getEventListeners(ac.signal).length, 0);
109+
assert.strictEqual(listenerCount(ac.signal, 'abort'), 0);
110110
assert.strictEqual(err, null);
111111
});
112112
execFile(process.execPath, [fixture, 0], { signal }, callback);

test/parallel/test-child-process-fork-timeout-kill-signal.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ const { mustCall } = require('../common');
44
const assert = require('assert');
55
const fixtures = require('../common/fixtures');
66
const { fork } = require('child_process');
7-
const { getEventListeners } = require('events');
7+
const { listenerCount } = require('events');
88

99
{
1010
// Verify default signal
@@ -43,8 +43,8 @@ const { getEventListeners } = require('events');
4343
timeout: 6,
4444
signal,
4545
});
46-
assert.strictEqual(getEventListeners(signal, 'abort').length, 1);
46+
assert.strictEqual(listenerCount(signal, 'abort'), 1);
4747
cp.on('exit', mustCall(() => {
48-
assert.strictEqual(getEventListeners(signal, 'abort').length, 0);
48+
assert.strictEqual(listenerCount(signal, 'abort'), 0);
4949
}));
5050
}

test/parallel/test-child-process-spawn-timeout-kill-signal.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ const { mustCall } = require('../common');
44
const assert = require('assert');
55
const fixtures = require('../common/fixtures');
66
const { spawn } = require('child_process');
7-
const { getEventListeners } = require('events');
7+
const { listenerCount } = require('events');
88

99
const aliveForeverFile = 'child-process-stay-alive-forever.js';
1010
{
@@ -43,8 +43,8 @@ const aliveForeverFile = 'child-process-stay-alive-forever.js';
4343
timeout: 6,
4444
signal,
4545
});
46-
assert.strictEqual(getEventListeners(signal, 'abort').length, 1);
46+
assert.strictEqual(listenerCount(signal, 'abort'), 1);
4747
cp.on('exit', mustCall(() => {
48-
assert.strictEqual(getEventListeners(signal, 'abort').length, 0);
48+
assert.strictEqual(listenerCount(signal, 'abort'), 0);
4949
}));
5050
}

test/parallel/test-events-once.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Flags: --no-warnings
33

44
const common = require('../common');
5-
const { once, EventEmitter, getEventListeners } = require('events');
5+
const { once, EventEmitter, listenerCount } = require('events');
66
const assert = require('assert');
77

88
async function onceAnEvent() {
@@ -72,7 +72,7 @@ async function catchesErrorsWithAbortSignal() {
7272
try {
7373
const promise = once(ee, 'myevent', { signal });
7474
assert.strictEqual(ee.listenerCount('error'), 1);
75-
assert.strictEqual(getEventListeners(signal, 'abort').length, 1);
75+
assert.strictEqual(listenerCount(signal, 'abort'), 1);
7676

7777
await promise;
7878
} catch (e) {
@@ -81,7 +81,7 @@ async function catchesErrorsWithAbortSignal() {
8181
assert.strictEqual(err, expected);
8282
assert.strictEqual(ee.listenerCount('error'), 0);
8383
assert.strictEqual(ee.listenerCount('myevent'), 0);
84-
assert.strictEqual(getEventListeners(signal, 'abort').length, 0);
84+
assert.strictEqual(listenerCount(signal, 'abort'), 0);
8585
}
8686

8787
async function stopListeningAfterCatchingError() {
@@ -191,9 +191,9 @@ async function abortSignalAfterEvent() {
191191
ac.abort();
192192
});
193193
const promise = once(ee, 'foo', { signal: ac.signal });
194-
assert.strictEqual(getEventListeners(ac.signal, 'abort').length, 1);
194+
assert.strictEqual(listenerCount(ac.signal, 'abort'), 1);
195195
await promise;
196-
assert.strictEqual(getEventListeners(ac.signal, 'abort').length, 0);
196+
assert.strictEqual(listenerCount(ac.signal, 'abort'), 0);
197197
}
198198

199199
async function abortSignalRemoveListener() {

test/parallel/test-eventtarget.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const {
99

1010
const assert = require('assert');
1111

12-
const { once } = require('events');
12+
const { listenerCount, once } = require('events');
1313

1414
const { inspect } = require('util');
1515
const { setTimeout: delay } = require('timers/promises');
@@ -141,10 +141,13 @@ let asyncTest = Promise.resolve();
141141

142142
eventTarget.addEventListener('foo', ev1);
143143
eventTarget.addEventListener('foo', ev2, { once: true });
144+
assert.strictEqual(listenerCount(eventTarget, 'foo'), 2);
144145
assert.ok(eventTarget.dispatchEvent(new Event('foo')));
146+
assert.strictEqual(listenerCount(eventTarget, 'foo'), 1);
145147
eventTarget.dispatchEvent(new Event('foo'));
146148

147149
eventTarget.removeEventListener('foo', ev1);
150+
assert.strictEqual(listenerCount(eventTarget, 'foo'), 0);
148151
eventTarget.dispatchEvent(new Event('foo'));
149152
}
150153
{

0 commit comments

Comments
 (0)