Implement an Event Emitter
Event Emitters are fundamental patterns in asynchronous programming, enabling objects to communicate with each other in a decoupled manner. They are crucial for building reactive systems, handling user interactions, and managing custom events within an application, allowing various parts of your codebase to react to specific occurrences without direct coupling.
Problem Description
Your task is to implement a core EventEmitter class or object that supports registering, unregistering, and emitting custom events. This implementation should allow for multiple listeners to subscribe to the same event and for these listeners to be invoked with any arguments passed during the event emission.
Specifically, your EventEmitter should provide the following public methods:
-
on(eventName, listener):- Registers a
listenerfunction to be called whenevereventNameis emitted. - The
listenerfunction should accept any number of arguments that are passed during theemitcall. - It should be possible to register the same
listenerfunction multiple times for the sameeventName(each registration counts as a separate subscription). - It should be possible to register different listeners for the same
eventName.
- Registers a
-
off(eventName, listener):- Removes one instance of the specified
listenerfunction from the listeners registered foreventName. - If the
listenerwas registered multiple times foreventName, only the first matching instance (or any single instance you consistently choose) should be removed. - If the
listeneroreventNamedoes not exist, no action should be taken.
- Removes one instance of the specified
-
emit(eventName, ...args):- Triggers all
listenerfunctions registered foreventName. - Each
listenerfunction should be called with...args(all arguments passed toemitaftereventName). - Listeners should be invoked in the order they were registered.
- If no listeners are registered for
eventName, nothing should happen.
- Triggers all
Examples
Example 1: Basic Event Emission
Input:
1. Initialize new EventEmitter: `emitter = new EventEmitter()`
2. Define listener functions:
`listener1(a, b) { print("Listener 1 received:", a, b) }`
`listener2(msg) { print("Listener 2 received:", msg) }`
3. Register listeners:
`emitter.on("data", listener1)`
`emitter.on("message", listener2)`
`emitter.on("data", listener2)` // listener2 also listens to "data"
4. Emit events:
`emitter.emit("data", 10, 20)`
`emitter.emit("message", "Hello World")`
Output:
Listener 1 received: 10 20
Listener 2 received: 10 20
Listener 2 received: Hello World
Explanation: When "data" is emitted, listener1 and listener2 are called in order with arguments (10, 20). When "message" is emitted, only listener2 is called with ("Hello World").
Example 2: Unsubscribing a Listener
Input:
1. Initialize new EventEmitter: `emitter = new EventEmitter()`
2. Define listener functions:
`logger(content) { print("Log:", content) }`
`analyzer(content) { print("Analyze:", content) }`
3. Register listeners:
`emitter.on("logEvent", logger)`
`emitter.on("logEvent", analyzer)`
`emitter.on("logEvent", logger)` // Register logger twice
4. Emit event:
`emitter.emit("logEvent", "First data")`
5. Unsubscribe one instance of logger:
`emitter.off("logEvent", logger)`
6. Emit event again:
`emitter.emit("logEvent", "Second data")`
Output:
Log: First data
Analyze: First data
Log: First data
Analyze: Second data
Log: Second data
Explanation: After the first emit, all three registered listeners are called. After emitter.off("logEvent", logger), one instance of logger is removed. So, the second emit calls analyzer and the remaining logger instance.
Example 3: Emitting with no listeners and invalid unsubscribe
Input:
1. Initialize new EventEmitter: `emitter = new EventEmitter()`
2. Define listener function:
`myListener() { print("My listener called") }`
`otherListener() { print("Other listener called") }`
3. Emit event with no listeners:
`emitter.emit("noSuchEvent", "arg")`
4. Register a listener:
`emitter.on("test", myListener)`
5. Attempt to unsubscribe a non-existent listener:
`emitter.off("test", otherListener)`
6. Emit the registered event:
`emitter.emit("test")`
Output:
My listener called
Explanation: Emitting "noSuchEvent" does nothing as no listeners are subscribed. Attempting to off a listener not subscribed to "test" also does nothing. Only myListener is called when "test" is emitted.
Constraints
- The number of distinct event names will not exceed 1000.
- The total number of listener registrations across all events will not exceed 10,000 at any given time.
- The
onandoffoperations should complete efficiently, ideally in O(1) or O(N) time where N is the number of listeners for a specific event. - The
emitoperation should complete in O(M) time, where M is the number of listeners subscribed to the emitted event. - Listener functions can accept any number and type of arguments.
- The system should be robust against invalid inputs (e.g., trying to remove a listener that wasn't registered).
Notes
- Consider using a suitable data structure (like a hash map or dictionary where keys are event names and values are lists of listeners) to store event names and their corresponding listeners.
- Pay attention to how you store and retrieve listener functions to ensure correct removal during the
offoperation. Since functions are first-class citizens in many languages, you can often use them directly as references. - The challenge focuses on the core
on,off, andemitfunctionality. Features likeonce(a listener that triggers only once then unsubscribes itself) are beyond the scope of this initial problem but are common extensions. - Ensure that when
emitis called, the list of listeners being iterated over is stable. This means thatonoroffcalls during anemitshould not affect the currentemitcycle; they should apply to the next emit cycle. A common approach for this is to iterate over a copy of the listeners array or snapshot it at the beginning ofemit.