Skip to content
JavaScript utilities · Senior · about 25 minutes

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.

  1. Hint 1

    Keep a Map from the event name to an array of listeners.

  2. Hint 2

    In emit, loop over a copy of the array. Then a listener may add or remove listeners without changing the loop.

  3. 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.