diff --git a/index.js b/index.js index 1612c7d..b7ed7b6 100644 --- a/index.js +++ b/index.js @@ -1,8 +1,7 @@ 'use strict'; -const anyMap = new WeakMap(); -const eventsMap = new WeakMap(); -const producersMap = new WeakMap(); +const {anyMap, producersMap, eventsMap} = require('./maps.js'); + const anyProducer = Symbol('anyProducer'); const resolvedPromise = Promise.resolve(); @@ -28,7 +27,7 @@ function assertListener(listener) { function getListeners(instance, eventName) { const events = eventsMap.get(instance); if (!events.has(eventName)) { - events.set(eventName, new Set()); + return; } return events.get(eventName); @@ -38,7 +37,7 @@ function getEventProducers(instance, eventName) { const key = typeof eventName === 'string' || typeof eventName === 'symbol' || typeof eventName === 'number' ? eventName : anyProducer; const producers = producersMap.get(instance); if (!producers.has(key)) { - producers.set(key, new Set()); + return; } return producers.get(key); @@ -79,7 +78,14 @@ function iterator(instance, eventNames) { }; for (const eventName of eventNames) { - getEventProducers(instance, eventName).add(producer); + let set = getEventProducers(instance, eventName); + if (!set) { + set = new Set(); + const producers = producersMap.get(instance); + producers.set(eventName, set); + } + + set.add(producer); } return { @@ -111,7 +117,14 @@ function iterator(instance, eventNames) { queue = undefined; for (const eventName of eventNames) { - getEventProducers(instance, eventName).delete(producer); + const set = getEventProducers(instance, eventName); + if (set) { + set.delete(producer); + if (set.size === 0) { + const producers = producersMap.get(instance); + producers.delete(eventName); + } + } } flush(); @@ -221,6 +234,9 @@ class Emittery { anyMap.set(this, new Set()); eventsMap.set(this, new Map()); producersMap.set(this, new Map()); + + producersMap.get(this).set(anyProducer, new Set()); + this.debug = options.debug || {}; if (this.debug.enabled === undefined) { @@ -259,7 +275,14 @@ class Emittery { eventNames = Array.isArray(eventNames) ? eventNames : [eventNames]; for (const eventName of eventNames) { assertEventName(eventName); - getListeners(this, eventName).add(listener); + let set = getListeners(this, eventName); + if (!set) { + set = new Set(); + const events = eventsMap.get(this); + events.set(eventName, set); + } + + set.add(listener); this.logIfDebugEnabled('subscribe', eventName, undefined); @@ -277,7 +300,14 @@ class Emittery { eventNames = Array.isArray(eventNames) ? eventNames : [eventNames]; for (const eventName of eventNames) { assertEventName(eventName); - getListeners(this, eventName).delete(listener); + const set = getListeners(this, eventName); + if (set) { + set.delete(listener); + if (set.size === 0) { + const events = eventsMap.get(this); + events.delete(eventName); + } + } this.logIfDebugEnabled('unsubscribe', eventName, undefined); @@ -321,7 +351,7 @@ class Emittery { enqueueProducers(this, eventName, eventData); - const listeners = getListeners(this, eventName); + const listeners = getListeners(this, eventName) || new Set(); const anyListeners = anyMap.get(this); const staticListeners = [...listeners]; const staticAnyListeners = isMetaEvent(eventName) ? [] : [...anyListeners]; @@ -350,7 +380,7 @@ class Emittery { this.logIfDebugEnabled('emitSerial', eventName, eventData); - const listeners = getListeners(this, eventName); + const listeners = getListeners(this, eventName) || new Set(); const anyListeners = anyMap.get(this); const staticListeners = [...listeners]; const staticAnyListeners = [...anyListeners]; @@ -401,28 +431,34 @@ class Emittery { this.logIfDebugEnabled('clear', eventName, undefined); if (typeof eventName === 'string' || typeof eventName === 'symbol' || typeof eventName === 'number') { - getListeners(this, eventName).clear(); + const set = getListeners(this, eventName); + if (set) { + set.clear(); + } const producers = getEventProducers(this, eventName); + if (producers) { + for (const producer of producers) { + producer.finish(); + } - for (const producer of producers) { - producer.finish(); + producers.clear(); } - - producers.clear(); } else { anyMap.get(this).clear(); - for (const listeners of eventsMap.get(this).values()) { + for (const [eventName, listeners] of eventsMap.get(this).entries()) { listeners.clear(); + eventsMap.get(this).delete(eventName); } - for (const producers of producersMap.get(this).values()) { + for (const [eventName, producers] of producersMap.get(this).entries()) { for (const producer of producers) { producer.finish(); } producers.clear(); + producersMap.get(this).delete(eventName); } } } @@ -434,8 +470,8 @@ class Emittery { for (const eventName of eventNames) { if (typeof eventName === 'string') { - count += anyMap.get(this).size + getListeners(this, eventName).size + - getEventProducers(this, eventName).size + getEventProducers(this).size; + count += anyMap.get(this).size + (getListeners(this, eventName) || new Set()).size + + (getEventProducers(this, eventName) || new Set()).size + (getEventProducers(this) || new Set()).size; continue; } diff --git a/maps.js b/maps.js new file mode 100644 index 0000000..7bf3421 --- /dev/null +++ b/maps.js @@ -0,0 +1,9 @@ +const anyMap = new WeakMap(); +const eventsMap = new WeakMap(); +const producersMap = new WeakMap(); + +module.exports = { + anyMap, + eventsMap, + producersMap +}; diff --git a/test/index.js b/test/index.js index 7de7788..03bae4a 100644 --- a/test/index.js +++ b/test/index.js @@ -2,6 +2,7 @@ import test from 'ava'; import delay from 'delay'; import pEvent from 'p-event'; import Emittery from '../index.js'; +import {eventsMap} from '../maps.js'; test('on()', async t => { const emitter = new Emittery(); @@ -348,6 +349,19 @@ test('off() - no listener', t => { }, TypeError); }); +test('off() - clears global maps when all listeners are removed', t => { + const emitter = new Emittery(); + + const event = 'string'; + const callback = () => {}; + + emitter.on(event, callback); + t.is(eventsMap.get(emitter).get(event).size, 1); + + emitter.off(event, callback); + t.is(eventsMap.get(emitter).get(event), undefined); +}); + test('once()', async t => { const fixture = '🌈'; const emitter = new Emittery();