Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions doc/api/deprecations.md
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,9 @@ The [`domain`][] module is deprecated and should not be used.

<!-- YAML
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/60214
description: Deprecation revoked.
- version:
- v6.12.0
- v4.8.6
Expand All @@ -796,10 +799,12 @@ changes:
description: Documentation-only deprecation.
-->

Type: Documentation-only
Type: Revoked

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

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

Expand Down Expand Up @@ -4398,7 +4403,7 @@ import { opendir } from 'node:fs/promises';
[`domain`]: domain.md
[`ecdh.setPublicKey()`]: crypto.md#ecdhsetpublickeypublickey-encoding
[`emitter.listenerCount(eventName)`]: events.md#emitterlistenercounteventname-listener
[`events.listenerCount(emitter, eventName)`]: events.md#eventslistenercountemitter-eventname
[`events.listenerCount(emitter, eventName)`]: events.md#eventslistenercountemitterortarget-eventname
[`fs.Dir`]: fs.md#class-fsdir
[`fs.FileHandle`]: fs.md#class-filehandle
[`fs.access()`]: fs.md#fsaccesspath-mode-callback
Expand Down
62 changes: 44 additions & 18 deletions doc/api/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -1622,39 +1622,66 @@

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

## `events.listenerCount(emitter, eventName)`
## `events.listenerCount(emitterOrTarget, eventName)`

<!-- YAML
added: v0.9.12
deprecated: v3.2.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/60214
description: Now accepts EventTarget arguments.
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/60214
description: Deprecation revoked.
- version: v3.2.0
pr-url: https://github.com/nodejs/node/pull/2349

Check warning on line 1637 in doc/api/events.md

View workflow job for this annotation

GitHub Actions / lint-pr-url

pr-url doesn't match the URL of the current PR.
description: Documentation-only deprecation.
-->

> Stability: 0 - Deprecated: Use [`emitter.listenerCount()`][] instead.
* `emitterOrTarget` {EventEmitter|EventTarget}
* `eventName` {string|symbol}
* Returns: {integer}

Returns the number of registered listeners for the event named `eventName`.

* `emitter` {EventEmitter} The emitter to query
* `eventName` {string|symbol} The event name
For `EventEmitter`s this behaves exactly the same as calling `.listenerCount`
on the emitter.

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

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

const myEmitter = new EventEmitter();
myEmitter.on('event', () => {});
myEmitter.on('event', () => {});
console.log(listenerCount(myEmitter, 'event'));
// Prints: 2
{
const ee = new EventEmitter();
ee.on('event', () => {});
ee.on('event', () => {});
console.log(listenerCount(ee, 'event')); // 2
}
{
const et = new EventTarget();
et.addEventListener('event', () => {});
et.addEventListener('event', () => {});
console.log(listenerCount(et, 'event')); // 2
}
```

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

const myEmitter = new EventEmitter();
myEmitter.on('event', () => {});
myEmitter.on('event', () => {});
console.log(listenerCount(myEmitter, 'event'));
// Prints: 2
{
const ee = new EventEmitter();
ee.on('event', () => {});
ee.on('event', () => {});
console.log(listenerCount(ee, 'event')); // 2
}
{
const et = new EventTarget();
et.addEventListener('event', () => {});
et.addEventListener('event', () => {});
console.log(listenerCount(et, 'event')); // 2
}
```

## `events.on(emitter, eventName[, options])`
Expand Down Expand Up @@ -2648,7 +2675,6 @@
[`Event` Web API]: https://dom.spec.whatwg.org/#event
[`domain`]: domain.md
[`e.stopImmediatePropagation()`]: #eventstopimmediatepropagation
[`emitter.listenerCount()`]: #emitterlistenercounteventname-listener
[`emitter.removeListener()`]: #emitterremovelistenereventname-listener
[`emitter.setMaxListeners(n)`]: #emittersetmaxlistenersn
[`event.defaultPrevented`]: #eventdefaultprevented
Expand Down
44 changes: 23 additions & 21 deletions lib/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ const {
Error,
ErrorCaptureStackTrace,
FunctionPrototypeBind,
FunctionPrototypeCall,
NumberMAX_SAFE_INTEGER,
ObjectDefineProperties,
ObjectDefineProperty,
Expand Down Expand Up @@ -215,6 +214,7 @@ module.exports.once = once;
module.exports.on = on;
module.exports.getEventListeners = getEventListeners;
module.exports.getMaxListeners = getMaxListeners;
module.exports.listenerCount = listenerCount;
// Backwards-compat with node 0.10.x
EventEmitter.EventEmitter = EventEmitter;

Expand Down Expand Up @@ -813,31 +813,14 @@ EventEmitter.prototype.rawListeners = function rawListeners(type) {
return _listeners(this, type, false);
};

/**
* Returns the number of listeners listening to the event name
* specified as `type`.
* @deprecated since v3.2.0
* @param {EventEmitter} emitter
* @param {string | symbol} type
* @returns {number}
*/
EventEmitter.listenerCount = function(emitter, type) {
if (typeof emitter.listenerCount === 'function') {
return emitter.listenerCount(type);
}
return FunctionPrototypeCall(listenerCount, emitter, type);
};

EventEmitter.prototype.listenerCount = listenerCount;

/**
* Returns the number of listeners listening to event name
* specified as `type`.
* @param {string | symbol} type
* @param {Function} listener
* @param {Function} [listener]
* @returns {number}
*/
function listenerCount(type, listener) {
EventEmitter.prototype.listenerCount = function listenerCount(type, listener) {
const events = this._events;

if (events !== undefined) {
Expand Down Expand Up @@ -867,7 +850,7 @@ function listenerCount(type, listener) {
}

return 0;
}
};

/**
* Returns an array listing the events for which
Expand Down Expand Up @@ -949,6 +932,25 @@ function getMaxListeners(emitterOrTarget) {
emitterOrTarget);
}

/**
* Returns the number of registered listeners for `type`.
* @param {EventEmitter | EventTarget} emitterOrTarget
* @param {string | symbol} type
* @returns {number}
*/
function listenerCount(emitterOrTarget, type) {
if (typeof emitterOrTarget.listenerCount === 'function') {
return emitterOrTarget.listenerCount(type);
}
const { isEventTarget, kEvents } = require('internal/event_target');
if (isEventTarget(emitterOrTarget)) {
return emitterOrTarget[kEvents].get(type)?.size ?? 0;
}
throw new ERR_INVALID_ARG_TYPE('emitter',
['EventEmitter', 'EventTarget'],
emitterOrTarget);
}

/**
* Creates a `Promise` that is fulfilled when the emitter
* emits the given event.
Expand Down
3 changes: 2 additions & 1 deletion lib/internal/streams/legacy.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ Stream.prototype.pipe = function(dest, options) {
// Don't leave dangling pipes when there are errors.
function onerror(er) {
cleanup();
if (EE.listenerCount(this, 'error') === 0) {
// If we removed the last error handler, trigger an unhandled error event.
if (this.listenerCount?.('error') === 0) {
Copy link
Member Author

@Renegade334 Renegade334 Oct 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Legacy behaviour dictates that stream.pipe() targets do not need to be true EventEmitters; this.listenerCount might not exist at all (#2655).

this.emit('error', er);
}
}
Expand Down
6 changes: 3 additions & 3 deletions test/parallel/test-aborted-util.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
const common = require('../common');
const { aborted } = require('util');
const assert = require('assert');
const { getEventListeners } = require('events');
const { listenerCount } = require('events');
const { inspect } = require('util');

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

test('Aborted with gc cleanup', async () => {
Expand All @@ -31,7 +31,7 @@ test('Aborted with gc cleanup', async () => {
globalThis.gc();
ac.abort();
assert.strictEqual(ac.signal.aborted, true);
assert.strictEqual(getEventListeners(ac.signal, 'abort').length, 0);
assert.strictEqual(listenerCount(ac.signal, 'abort'), 0);
resolve();
}));

Expand Down
4 changes: 2 additions & 2 deletions test/parallel/test-child-process-execfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
const common = require('../common');
const assert = require('assert');
const { execFile, execFileSync } = require('child_process');
const { getEventListeners } = require('events');
const { listenerCount } = require('events');
const { getSystemErrorName } = require('util');
const fixtures = require('../common/fixtures');
const os = require('os');
Expand Down Expand Up @@ -106,7 +106,7 @@ common.expectWarning(
const { signal } = ac;

const callback = common.mustCall((err) => {
assert.strictEqual(getEventListeners(ac.signal).length, 0);
assert.strictEqual(listenerCount(ac.signal, 'abort'), 0);
assert.strictEqual(err, null);
});
execFile(process.execPath, [fixture, 0], { signal }, callback);
Expand Down
6 changes: 3 additions & 3 deletions test/parallel/test-child-process-fork-timeout-kill-signal.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const { mustCall } = require('../common');
const assert = require('assert');
const fixtures = require('../common/fixtures');
const { fork } = require('child_process');
const { getEventListeners } = require('events');
const { listenerCount } = require('events');

{
// Verify default signal
Expand Down Expand Up @@ -43,8 +43,8 @@ const { getEventListeners } = require('events');
timeout: 6,
signal,
});
assert.strictEqual(getEventListeners(signal, 'abort').length, 1);
assert.strictEqual(listenerCount(signal, 'abort'), 1);
cp.on('exit', mustCall(() => {
assert.strictEqual(getEventListeners(signal, 'abort').length, 0);
assert.strictEqual(listenerCount(signal, 'abort'), 0);
}));
}
6 changes: 3 additions & 3 deletions test/parallel/test-child-process-spawn-timeout-kill-signal.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const { mustCall } = require('../common');
const assert = require('assert');
const fixtures = require('../common/fixtures');
const { spawn } = require('child_process');
const { getEventListeners } = require('events');
const { listenerCount } = require('events');

const aliveForeverFile = 'child-process-stay-alive-forever.js';
{
Expand Down Expand Up @@ -43,8 +43,8 @@ const aliveForeverFile = 'child-process-stay-alive-forever.js';
timeout: 6,
signal,
});
assert.strictEqual(getEventListeners(signal, 'abort').length, 1);
assert.strictEqual(listenerCount(signal, 'abort'), 1);
cp.on('exit', mustCall(() => {
assert.strictEqual(getEventListeners(signal, 'abort').length, 0);
assert.strictEqual(listenerCount(signal, 'abort'), 0);
}));
}
10 changes: 5 additions & 5 deletions test/parallel/test-events-once.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Flags: --no-warnings

const common = require('../common');
const { once, EventEmitter, getEventListeners } = require('events');
const { once, EventEmitter, listenerCount } = require('events');
const assert = require('assert');

async function onceAnEvent() {
Expand Down Expand Up @@ -72,7 +72,7 @@ async function catchesErrorsWithAbortSignal() {
try {
const promise = once(ee, 'myevent', { signal });
assert.strictEqual(ee.listenerCount('error'), 1);
assert.strictEqual(getEventListeners(signal, 'abort').length, 1);
assert.strictEqual(listenerCount(signal, 'abort'), 1);

await promise;
} catch (e) {
Expand All @@ -81,7 +81,7 @@ async function catchesErrorsWithAbortSignal() {
assert.strictEqual(err, expected);
assert.strictEqual(ee.listenerCount('error'), 0);
assert.strictEqual(ee.listenerCount('myevent'), 0);
assert.strictEqual(getEventListeners(signal, 'abort').length, 0);
assert.strictEqual(listenerCount(signal, 'abort'), 0);
}

async function stopListeningAfterCatchingError() {
Expand Down Expand Up @@ -191,9 +191,9 @@ async function abortSignalAfterEvent() {
ac.abort();
});
const promise = once(ee, 'foo', { signal: ac.signal });
assert.strictEqual(getEventListeners(ac.signal, 'abort').length, 1);
assert.strictEqual(listenerCount(ac.signal, 'abort'), 1);
await promise;
assert.strictEqual(getEventListeners(ac.signal, 'abort').length, 0);
assert.strictEqual(listenerCount(ac.signal, 'abort'), 0);
}

async function abortSignalRemoveListener() {
Expand Down
5 changes: 4 additions & 1 deletion test/parallel/test-eventtarget.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const {

const assert = require('assert');

const { once } = require('events');
const { listenerCount, once } = require('events');

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

eventTarget.addEventListener('foo', ev1);
eventTarget.addEventListener('foo', ev2, { once: true });
assert.strictEqual(listenerCount(eventTarget, 'foo'), 2);
assert.ok(eventTarget.dispatchEvent(new Event('foo')));
assert.strictEqual(listenerCount(eventTarget, 'foo'), 1);
eventTarget.dispatchEvent(new Event('foo'));

eventTarget.removeEventListener('foo', ev1);
assert.strictEqual(listenerCount(eventTarget, 'foo'), 0);
eventTarget.dispatchEvent(new Event('foo'));
}
{
Expand Down
Loading
Loading