Skip to main content

EventEmitterII

// You are free to use alternative approaches of
// instantiating the EventEmitter as long as the
// default export is correct.
export default class EventEmitter {
constructor() {
// eventName -> [listener1, listener2, ...]
// Map gives O(1)-ish lookup by event name.
this.events = new Map();
}

/**
* @param {string} eventName
* @param {Function} listener
* @returns {{off: Function}}
*/
on(eventName, listener) {
if (!this.events.has(eventName)) {
this.events.set(eventName, []);
}

const listeners = this.events.get(eventName);
// Keep insertion order so listeners run in subscribe order.
listeners.push(listener);

// Common interview API shape: return a handle with `off()`.
// This closure keeps access to the exact listeners array.
return {
off: () => {
const index = listeners.indexOf(listener);
if (index !== -1) {
// Remove only this listener instance.
listeners.splice(index, 1);
}
},
};
}

/**
* @param {string} eventName
* @param {...any} args
* @returns boolean
*/
emit(eventName, ...args) {
// Match common EventEmitter behavior: return false if nothing is emitted.
if (!this.events.has(eventName)) {
return false;
}

const listeners = this.events.get(eventName);
if (listeners.length === 0) {
return false;
}

// Fan out payload to each subscriber.
for (const listener of listeners) {
listener(...args);
}

// At least one listener ran.
return true;
}
}