Event emitter
Implement class EventEmitter with on(event, listener), off(event, listener), once(event, listener) and emit(event, ...args). on returns a function that removes that listener. emit calls the listeners in the order they were added and returns true when there was at least one. A listener removed while an emit is running still gets that emit; one added during an emit waits for the next. In the tests a listener named A logs [A, ...args]; A!B also removes B; A+C also adds C.
Start from
class EventEmitter {
on(event, listener) {
// Add the listener and return a function that removes it.
}
off(event, listener) {}
once(event, listener) {}
emit(event, ...args) {}
}Hints
Each hint gives away a little more. Open one only when you need it.
Hint 1
Keep a Map from the event name to an array of listeners.
Hint 2
In emit, loop over a copy of the array. Then a listener may add or remove listeners without changing the loop.
Hint 3
once wraps the listener in a function that removes itself and then calls the listener.
Solution
From here on the page gives the method away. If you are still solving, go back to the hints.
JavaScript
class EventEmitter {
constructor() {
this.listeners = new Map();
}
on(event, listener) {
if (!this.listeners.has(event)) this.listeners.set(event, []);
this.listeners.get(event).push(listener);
return () => this.off(event, listener);
}
off(event, listener) {
const list = this.listeners.get(event);
if (!list) return;
const index = list.indexOf(listener);
if (index >= 0) list.splice(index, 1);
}
once(event, listener) {
const wrapper = (...args) => {
this.off(event, wrapper);
listener(...args);
};
return this.on(event, wrapper);
}
emit(event, ...args) {
const list = [...(this.listeners.get(event) ?? [])];
for (const listener of list) listener(...args);
return list.length > 0;
}
}What an interviewer looks for
The observer pattern is behind DOM events, stores and sockets. Interviewers look at what happens when listeners change during an emit, and at how a subscriber cleans up.