Hone logo
Hone
Problems

JavaScript Event Emitter

This challenge asks you to build a fundamental building block for many asynchronous JavaScript applications: an event emitter. An event emitter allows objects to communicate with each other by emitting named events and allowing other objects to subscribe to and react to those events. This pattern is crucial for decoupling components and managing complex application logic.

Problem Description

You need to create a JavaScript class named EventEmitter. This class should provide a mechanism for objects to:

  1. Register listeners: Allow functions (listeners) to be attached to specific event names.
  2. Emit events: Trigger registered listeners when a specific event occurs.
  3. Remove listeners: Allow registered listeners to be detached from events.

Key Requirements:

  • on(eventName, listener):
    • Registers a listener function to be called whenever an event named eventName is emitted.
    • Multiple listeners can be registered for the same event.
    • The listener function should be called with any arguments passed when the event is emitted.
  • emit(eventName, ...args):
    • Emits an event named eventName.
    • All listeners registered for eventName should be called.
    • Any ...args passed to emit should be passed as arguments to the listener functions.
    • If no listeners are registered for an event, emit should do nothing.
  • off(eventName, listener):
    • Removes a specific listener function from the event eventName.
    • If the listener is not found, off should do nothing.
    • If multiple identical listeners are registered for the same event, off should remove only one instance.

Expected Behavior:

  • Listeners should be called in the order they were registered.
  • Removing a listener should prevent it from being called for subsequent emit calls.
  • Emitting an event with arguments should pass those arguments to the listeners.

Edge Cases to Consider:

  • Emitting an event that has no listeners.
  • Removing a listener that was never registered.
  • Removing a listener that is not associated with a specific event.
  • Registering the same listener multiple times for the same event.

Examples

Example 1: Basic Event Emission and Listening

const emitter = new EventEmitter();

const greetListener = (name) => {
  console.log(`Hello, ${name}!`);
};

emitter.on('greet', greetListener);
emitter.emit('greet', 'Alice'); // Output: Hello, Alice!

Explanation: A listener function greetListener is registered for the 'greet' event. When emit('greet', 'Alice') is called, the greetListener is executed with 'Alice' as an argument, printing the greeting.

Example 2: Multiple Listeners for the Same Event

const emitter = new EventEmitter();

const listener1 = () => console.log('Listener 1 fired');
const listener2 = () => console.log('Listener 2 fired');

emitter.on('data', listener1);
emitter.on('data', listener2);
emitter.emit('data');
// Output:
// Listener 1 fired
// Listener 2 fired

Explanation: Two listeners are registered for the 'data' event. When 'data' is emitted, both listeners are called in the order they were registered.

Example 3: Removing a Listener

const emitter = new EventEmitter();

const specialListener = () => console.log('This is special!');
const regularListener = () => console.log('This is regular.');

emitter.on('notify', specialListener);
emitter.on('notify', regularListener);

emitter.emit('notify');
// Output:
// This is special!
// This is regular.

emitter.off('notify', specialListener);
emitter.emit('notify');
// Output:
// This is regular.

Explanation: Initially, both specialListener and regularListener are called. After specialListener is removed using off, only regularListener is called for subsequent 'notify' events.

Example 4: Emitting with Multiple Arguments

const emitter = new EventEmitter();

const sumListener = (a, b, c) => {
  console.log(`Sum: ${a + b + c}`);
};

emitter.on('calculate', sumListener);
emitter.emit('calculate', 5, 10, 15); // Output: Sum: 30

Explanation: The calculate event is emitted with three arguments. The sumListener receives these arguments and calculates their sum.

Example 5: Edge Case - Emitting an Unhandled Event

const emitter = new EventEmitter();
emitter.emit('nonExistentEvent', 'some data'); // No output or error

Explanation: If an event is emitted for which no listeners are registered, the EventEmitter should gracefully do nothing.

Example 6: Edge Case - Removing a Non-existent Listener

const emitter = new EventEmitter();
const listener = () => {};
emitter.on('event', listener);
emitter.off('event', () => {}); // Attempting to remove a different function instance
emitter.emit('event'); // Output: listener is still called

Explanation: Attempting to remove a listener that is not the exact function reference registered will not remove the intended listener.

Constraints

  • The EventEmitter class should be implemented using plain JavaScript.
  • No external libraries are allowed.
  • The implementation should be efficient enough to handle a reasonable number of events and listeners (e.g., thousands).
  • Listeners for a given event should be called synchronously in the order they were registered.

Notes

  • Consider how you will store the listeners for each event. A common approach is to use an object where keys are event names and values are arrays of listener functions.
  • Think about how to handle the this context within your listeners if you intend to support that (though for this basic challenge, it's not strictly required).
  • The off method should correctly handle cases where multiple identical listener functions might be registered for the same event.
Loading editor...
javascript