Skip to content

Commit 3641e7a

Browse files
Properly clean up listener storage (#103)
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
1 parent ca9ed93 commit 3641e7a

File tree

3 files changed

+79
-20
lines changed

3 files changed

+79
-20
lines changed

index.js

Lines changed: 56 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
'use strict';
22

3-
const anyMap = new WeakMap();
4-
const eventsMap = new WeakMap();
5-
const producersMap = new WeakMap();
3+
const {anyMap, producersMap, eventsMap} = require('./maps.js');
4+
65
const anyProducer = Symbol('anyProducer');
76
const resolvedPromise = Promise.resolve();
87

@@ -28,7 +27,7 @@ function assertListener(listener) {
2827
function getListeners(instance, eventName) {
2928
const events = eventsMap.get(instance);
3029
if (!events.has(eventName)) {
31-
events.set(eventName, new Set());
30+
return;
3231
}
3332

3433
return events.get(eventName);
@@ -38,7 +37,7 @@ function getEventProducers(instance, eventName) {
3837
const key = typeof eventName === 'string' || typeof eventName === 'symbol' || typeof eventName === 'number' ? eventName : anyProducer;
3938
const producers = producersMap.get(instance);
4039
if (!producers.has(key)) {
41-
producers.set(key, new Set());
40+
return;
4241
}
4342

4443
return producers.get(key);
@@ -79,7 +78,14 @@ function iterator(instance, eventNames) {
7978
};
8079

8180
for (const eventName of eventNames) {
82-
getEventProducers(instance, eventName).add(producer);
81+
let set = getEventProducers(instance, eventName);
82+
if (!set) {
83+
set = new Set();
84+
const producers = producersMap.get(instance);
85+
producers.set(eventName, set);
86+
}
87+
88+
set.add(producer);
8389
}
8490

8591
return {
@@ -111,7 +117,14 @@ function iterator(instance, eventNames) {
111117
queue = undefined;
112118

113119
for (const eventName of eventNames) {
114-
getEventProducers(instance, eventName).delete(producer);
120+
const set = getEventProducers(instance, eventName);
121+
if (set) {
122+
set.delete(producer);
123+
if (set.size === 0) {
124+
const producers = producersMap.get(instance);
125+
producers.delete(eventName);
126+
}
127+
}
115128
}
116129

117130
flush();
@@ -221,6 +234,9 @@ class Emittery {
221234
anyMap.set(this, new Set());
222235
eventsMap.set(this, new Map());
223236
producersMap.set(this, new Map());
237+
238+
producersMap.get(this).set(anyProducer, new Set());
239+
224240
this.debug = options.debug || {};
225241

226242
if (this.debug.enabled === undefined) {
@@ -259,7 +275,14 @@ class Emittery {
259275
eventNames = Array.isArray(eventNames) ? eventNames : [eventNames];
260276
for (const eventName of eventNames) {
261277
assertEventName(eventName);
262-
getListeners(this, eventName).add(listener);
278+
let set = getListeners(this, eventName);
279+
if (!set) {
280+
set = new Set();
281+
const events = eventsMap.get(this);
282+
events.set(eventName, set);
283+
}
284+
285+
set.add(listener);
263286

264287
this.logIfDebugEnabled('subscribe', eventName, undefined);
265288

@@ -277,7 +300,14 @@ class Emittery {
277300
eventNames = Array.isArray(eventNames) ? eventNames : [eventNames];
278301
for (const eventName of eventNames) {
279302
assertEventName(eventName);
280-
getListeners(this, eventName).delete(listener);
303+
const set = getListeners(this, eventName);
304+
if (set) {
305+
set.delete(listener);
306+
if (set.size === 0) {
307+
const events = eventsMap.get(this);
308+
events.delete(eventName);
309+
}
310+
}
281311

282312
this.logIfDebugEnabled('unsubscribe', eventName, undefined);
283313

@@ -321,7 +351,7 @@ class Emittery {
321351

322352
enqueueProducers(this, eventName, eventData);
323353

324-
const listeners = getListeners(this, eventName);
354+
const listeners = getListeners(this, eventName) || new Set();
325355
const anyListeners = anyMap.get(this);
326356
const staticListeners = [...listeners];
327357
const staticAnyListeners = isMetaEvent(eventName) ? [] : [...anyListeners];
@@ -350,7 +380,7 @@ class Emittery {
350380

351381
this.logIfDebugEnabled('emitSerial', eventName, eventData);
352382

353-
const listeners = getListeners(this, eventName);
383+
const listeners = getListeners(this, eventName) || new Set();
354384
const anyListeners = anyMap.get(this);
355385
const staticListeners = [...listeners];
356386
const staticAnyListeners = [...anyListeners];
@@ -401,28 +431,34 @@ class Emittery {
401431
this.logIfDebugEnabled('clear', eventName, undefined);
402432

403433
if (typeof eventName === 'string' || typeof eventName === 'symbol' || typeof eventName === 'number') {
404-
getListeners(this, eventName).clear();
434+
const set = getListeners(this, eventName);
435+
if (set) {
436+
set.clear();
437+
}
405438

406439
const producers = getEventProducers(this, eventName);
440+
if (producers) {
441+
for (const producer of producers) {
442+
producer.finish();
443+
}
407444

408-
for (const producer of producers) {
409-
producer.finish();
445+
producers.clear();
410446
}
411-
412-
producers.clear();
413447
} else {
414448
anyMap.get(this).clear();
415449

416-
for (const listeners of eventsMap.get(this).values()) {
450+
for (const [eventName, listeners] of eventsMap.get(this).entries()) {
417451
listeners.clear();
452+
eventsMap.get(this).delete(eventName);
418453
}
419454

420-
for (const producers of producersMap.get(this).values()) {
455+
for (const [eventName, producers] of producersMap.get(this).entries()) {
421456
for (const producer of producers) {
422457
producer.finish();
423458
}
424459

425460
producers.clear();
461+
producersMap.get(this).delete(eventName);
426462
}
427463
}
428464
}
@@ -434,8 +470,8 @@ class Emittery {
434470

435471
for (const eventName of eventNames) {
436472
if (typeof eventName === 'string') {
437-
count += anyMap.get(this).size + getListeners(this, eventName).size +
438-
getEventProducers(this, eventName).size + getEventProducers(this).size;
473+
count += anyMap.get(this).size + (getListeners(this, eventName) || new Set()).size +
474+
(getEventProducers(this, eventName) || new Set()).size + (getEventProducers(this) || new Set()).size;
439475
continue;
440476
}
441477

maps.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const anyMap = new WeakMap();
2+
const eventsMap = new WeakMap();
3+
const producersMap = new WeakMap();
4+
5+
module.exports = {
6+
anyMap,
7+
eventsMap,
8+
producersMap
9+
};

test/index.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import test from 'ava';
22
import delay from 'delay';
33
import pEvent from 'p-event';
44
import Emittery from '../index.js';
5+
import {eventsMap} from '../maps.js';
56

67
test('on()', async t => {
78
const emitter = new Emittery();
@@ -348,6 +349,19 @@ test('off() - no listener', t => {
348349
}, TypeError);
349350
});
350351

352+
test('off() - clears global maps when all listeners are removed', t => {
353+
const emitter = new Emittery();
354+
355+
const event = 'string';
356+
const callback = () => {};
357+
358+
emitter.on(event, callback);
359+
t.is(eventsMap.get(emitter).get(event).size, 1);
360+
361+
emitter.off(event, callback);
362+
t.is(eventsMap.get(emitter).get(event), undefined);
363+
});
364+
351365
test('once()', async t => {
352366
const fixture = '🌈';
353367
const emitter = new Emittery();

0 commit comments

Comments
 (0)