Build an event emitter
Implement a class EventEmitter with these methods:
on(event, listener)registers a listener and returnsthisoff(event, listener)removes that listener (also when it was added withonce) and returnsthisonce(event, listener)registers a listener that is removed just before its first run, and returnsthisemit(event, ...args)calls the listeners foreventin the order they were added, passingargs, and returnstrueif the event had listeners andfalseotherwise (like Node)
Removing listeners while an event is being emitted (for example, a once listener removing itself) must not cause other listeners of that same emit to be skipped. Removing an unknown listener or event does nothing.
emitter.on('greet', (name) => console.log('hi ' + name)), thenemitter.emit('greet', 'Ada')→ logshi Adaand returnstrueemitter.emit('nothing')→false- after
emitter.once('ready', fn), the firstemit('ready')callsfnand the second returnsfalse
Define EventEmitter in the editor. 7 tests will call it.
Hint 1Store listeners in a Map (or object) from event name to an array of functions, and return this from on, off and once.
Hint 2For once, register a wrapper that removes itself and then calls the real listener. Save the original on the wrapper (wrapper.listener = listener) so off can match either one.
Hint 3In emit, loop over a copy of the array ([...listeners]) so listeners that remove themselves don't shift the ones you haven't called yet.
one clean solution
class EventEmitter {
constructor() {
this.events = new Map(); // event name -> array of listeners
}
on(event, listener) {
if (!this.events.has(event)) this.events.set(event, []);
this.events.get(event).push(listener);
return this;
}
off(event, listener) {
const listeners = this.events.get(event);
if (!listeners) return this;
const index = listeners.findIndex((l) => l === listener || l.listener === listener);
if (index !== -1) listeners.splice(index, 1);
if (listeners.length === 0) this.events.delete(event);
return this;
}
once(event, listener) {
const wrapper = (...args) => {
this.off(event, wrapper);
listener.apply(this, args);
};
wrapper.listener = listener; // lets off(event, listener) find the wrapper
return this.on(event, wrapper);
}
emit(event, ...args) {
const listeners = this.events.get(event);
if (!listeners || listeners.length === 0) return false;
// Iterate over a copy so listeners removed during this emit don't shift the rest.
for (const listener of [...listeners]) listener.apply(this, args);
return true;
}
}