Skip to content

node:events EventEmitter — listenerCount, eventNames, once, prependListener etc. return wrong values; static helpers broken #850

@proggeramlug

Description

@proggeramlug

Surfaced by the per-module parity matrix (#812 baseline; `events` is at diff=26).

EventEmitter is foundational — `net`, `http`, `stream`, `child_process`, and most npm packages use it as a base. A correctness gap here cascades. The good news is many of the broken methods look like they share a single root cause (the internal listener-table representation), so a single fix likely repairs much of the diff.

Specific gaps (Perry vs Node)

Probe Node Perry
`em.listenerCount('inc')` (after 2 .on()) `2` `0`
`em.eventNames()` `[ 'inc' ]` `0` (wrong type entirely)
`em.once → handler arg captured` `[ 7 ]` `[]`
`em.addListener / removeListener` `[ 1 ]` `[]`
`em.prependListener` order `[ 'a', 'b' ]` `[ 'b' ]` (only second listener kept)
`em.prependOnceListener` order `[ 'a', 'b', 'b' ]` `[ 'b', 'b' ]`
`em.listeners('inc')` count `2` `undefined`
`em.rawListeners('inc')` count `2` `undefined`
`em.getMaxListeners()` after setMaxListeners(42) `42` `0` (setMax doesn't persist)
`em.removeAllListeners()` then names `[]` `0`
`events.once(em,'x')` (module-level helper) resolves to array throws "Cannot read properties of undefined (reading 'length')"
`events.on(em,'evt')` async-iterator `[ 1, 2, 3 ]` `[]`
`events.getEventListeners(em,'e')` array throws same
`events.getMaxListeners(em)` number undefined
`events.setMaxListeners(em,20) → get` `20` `0`
`events.listenerCount(em,'e')` static helper `3` `undefined`
`EventEmitter.listenerCount(em,'e')` static helper `3` `undefined`
`events.addAbortListener(signal, fn)` fires + returns Disposable not invoked
`events.defaultMaxListeners` `10` (missing)
`events.errorMonitor` / `events.captureRejectionSymbol` symbols (missing)
`'newListener'` / `'removeListener'` synthetic events array of names not emitted

Root-cause hypothesis

The pattern "listenerCount → 0", "eventNames → 0", "listeners → undefined" is consistent with the internal listener map either not being populated by `.on()` / `.addListener()`, or being populated under a different per-instance key than the readers read from. The `prependListener` case is the most diagnostic: when called twice, only the second listener is retained — pointing at the on-store side overwriting rather than appending.

`events.once` / `events.on` / `events.getEventListeners` all bottom out in "`Cannot read properties of undefined`" — those are module-level helpers reaching into the EE's internal state via a getter that's missing entirely.

`events.defaultMaxListeners`, `events.errorMonitor`, `events.captureRejectionSymbol` are simple exported constants the module never exposed — quick wins.

Why this matters

A working EventEmitter is a prerequisite for: `net`, `http` (`server.on('request')`), `stream` (`.on('data')`, `.on('end')`, `.on('error')`), `child_process`, any `Readable`/`Writable` flow, and ~most npm packages that emit progress / lifecycle events. Several other parity-matrix modules with large diffs (`net` at 63, `stream` at 78, `http` at 259) probably collapse significantly once `events` is correct, because the test programs there call `.on()` / `listenerCount()` / `listeners()` to introspect state.

Surface impact

Probably the single highest-leverage parity fix on the matrix today, ahead of the #841 submodule cluster (which is wider but lower-impact per module). Closing this likely pulls `events` to PASS and meaningfully cuts the diffs on net/stream/http.

Part of #793. Surfaced by the #812 per-module matrix baseline.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingparityNode.js compatibility / parity gaps

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions