Skip to content

Commit cfdab84

Browse files
committed
Simplify event emitter
1 parent 5daa5d0 commit cfdab84

2 files changed

Lines changed: 46 additions & 22 deletions

File tree

src/Emitter.js

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,33 @@
1-
// EventTarget mixin — shared by DspObject and AudioPort
2-
// Adds on/off/once/emit/removeAllListeners/listenerCount to any EventTarget subclass
1+
// Tiny event emitter — extends EventTarget for instanceof conformance,
2+
// but overrides addEventListener/removeEventListener/dispatchEvent with own tracking.
3+
// No Node.js maxListeners warnings, no duplicate tracking, no platform dependency.
34

4-
export default (Base = EventTarget) => class Emitter extends Base {
5-
#listeners = []
5+
export default () => class Emitter extends EventTarget {
6+
#events = new Map()
67

7-
on(type, fn) {
8-
this.#listeners.push({ type, fn })
9-
this.addEventListener(type, fn)
8+
addEventListener(type, fn) {
9+
if (!fn) return
10+
let s = this.#events.get(type)
11+
if (!s) this.#events.set(type, s = new Set())
12+
s.add(fn)
1013
}
1114

12-
off(type, fn) {
13-
this.#listeners = this.#listeners.filter(l => !(l.type === type && l.fn === fn))
14-
this.removeEventListener(type, fn)
15+
removeEventListener(type, fn) {
16+
this.#events.get(type)?.delete(fn)
1517
}
1618

19+
dispatchEvent(event) {
20+
let s = this.#events.get(event.type)
21+
if (s) for (let fn of s) fn.call(this, event)
22+
return true
23+
}
24+
25+
on(type, fn) { this.addEventListener(type, fn) }
26+
off(type, fn) { this.removeEventListener(type, fn) }
27+
1728
once(type, fn) {
18-
let wrapper = (e) => { fn(e); this.off(type, wrapper) }
19-
this.on(type, wrapper)
29+
let w = (e) => { fn(e); this.off(type, w) }
30+
this.on(type, w)
2031
}
2132

2233
emit(type, detail) {
@@ -25,17 +36,10 @@ export default (Base = EventTarget) => class Emitter extends Base {
2536
this.dispatchEvent(e)
2637
}
2738

28-
listenerCount(type) {
29-
return this.#listeners.filter(l => l.type === type).length
30-
}
39+
listenerCount(type) { return this.#events.get(type)?.size || 0 }
3140

3241
removeAllListeners(type) {
33-
if (type) {
34-
for (let l of this.#listeners) if (l.type === type) this.removeEventListener(l.type, l.fn)
35-
this.#listeners = this.#listeners.filter(l => l.type !== type)
36-
} else {
37-
for (let l of this.#listeners) this.removeEventListener(l.type, l.fn)
38-
this.#listeners = []
39-
}
42+
if (type) this.#events.delete(type)
43+
else this.#events.clear()
4044
}
4145
}

test/audit-fixes.test.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,26 @@ test('listener cleanup > disconnect only removes own handler', () => {
134134
is(output.listenerCount('_numberOfChannels'), 0, 'all cleaned up')
135135
})
136136

137+
test('listener cleanup > fan-out beyond 10 connections emits no warning', () => {
138+
let ctx = { sampleRate: 44100 }
139+
let output = new AudioOutput(ctx, {}, 0)
140+
let inputs = Array.from({ length: 16 }, () =>
141+
new AudioInput(ctx, { channelCount: 1, channelCountMode: 'max', channelInterpretation: 'discrete' }, 0))
142+
143+
// connect 16 inputs to same output — must not trigger MaxListenersExceededWarning
144+
let warned = false
145+
let onWarn = (w) => { if (w.name === 'MaxListenersExceededWarning') warned = true }
146+
process.on('warning', onWarn)
147+
for (let inp of inputs) inp.connect(output)
148+
is(output.listenerCount('_numberOfChannels'), 16, '16 listeners registered')
149+
ok(!warned, 'no MaxListenersExceededWarning')
150+
151+
// cleanup
152+
for (let inp of inputs) inp.disconnect(output)
153+
is(output.listenerCount('_numberOfChannels'), 0, 'all cleaned up after disconnect')
154+
process.off('warning', onWarn)
155+
})
156+
137157
test.mute('cancelScheduledValues > removes future events', () => {
138158
let ctx = { currentTime: 0, sampleRate: 44100 }
139159
let p = new AudioParam(ctx, 0, 'a')

0 commit comments

Comments
 (0)