Event Emitter
Prompt
Your task is to build a basic EventEmitter class in JavaScript. Event emitters are a common pattern used in Node.js and the browser to handle asynchronous actions (like responding to a user click or a network response).
Your class must implement the following three methods:
on(eventName, listener): Subscribes alistenerfunction to a specificeventName. Multiple listeners can be subscribed to the same event.off(eventName, listener): Unsubscribes a specificlistenerfunction from theeventName. If the listener isn't subscribed, it should do nothing.emit(eventName, ...args): Triggers all listeners subscribed to theeventNamein the order they were added, passing any providedargsto each listener. If no listeners are subscribed to the event, it should do nothing.
Requirements:
- You should handle the case where multiple functions are registered to the same event.
- You should handle emitting events that have no listeners gracefully (without throwing an error).
- Listeners might need to receive an arbitrary number of arguments when an event is emitted.
Playground
You'll need a way to keep track of event names and their corresponding listeners. Think about what data structure is best for mapping a string (the event name) to a list of functions.
When someone calls on for an event that hasn't been used
yet, you might need to initialize an empty collection for
that event before adding the listener to it.
To remove a listener in off, you can use array methods
that create a new array excluding the specified item, or
find its index and remove it directly.
When emit is called, you'll need to check if the event
exists in your tracking structure. If it does, loop
through its collection of listeners and call each one,
passing along all the ...args.
Solution
Explanation
What is an Event Emitter?
Imagine Bob and Alice are in a group chat. They want to be notified whenever a 'newMessage' arrives. They can subscribe to the chat, they can unsubscribe if they leave, and whenever someone types a message, the chat application emits it to everyone currently subscribed.
An EventEmitter in code works exactly the same way!
onis like subscribing to the group chat.offis like leaving the chat (unsubscribing).emitis like sending a new message to the chat.
Instead of Bob and Alice the people, they subscribe with functions (called listeners). Instead of a chat message, you emit data (arguments).
How it works under the hood
To make this work, our class needs to keep a record of who is subscribed to what events. We use a regular JavaScript object to act as our "database" of subscriptions. We can call it this.events.
this.events = {
newMessage: [bobListener, aliceListener],
userJoined: [someOtherListener],
};Registering Listeners (on)
When Bob wants to join, we call eventEmitter.on('newMessage', bobListener). We need to add bobListener to the list.
- First, we check if
'newMessage'already exists in ourthis.eventsobject. If it doesn't, we create an empty array for it. - Then, we just
.push()his listener function into that array.
Removing Listeners (off)
When Alice leaves the chat, she calls eventEmitter.off('newMessage', aliceListener) to unsubscribe.
- We look up
'newMessage'in our object. - If it exists, we take the array of functions and filter out
aliceListener. We can use the.filter()method to create a new array that only includes the functions that are notaliceListener. This removes her from the list!
Emitting Events (emit)
When it's time to send out a message, the system calls eventEmitter.emit('newMessage', 'Hello, everyone!').
- We look up
'newMessage'in our object. - If we find an array of functions (like Bob and Alice's listeners), we loop through it using
.forEach(). - For every function in the array, we call it and pass in the data (
'Hello, everyone!'). We use the spread operator (...args) so we can pass any number of arguments the event might need.
By combining an object (to look up event names quickly) and arrays (to keep lists of functions in order), we can easily manage our own custom event system!
Real-World Use Cases
Event Emitters aren't just interview questions; they're the invisible plumbing of the web!
- The DOM: Every time you write
document.addEventListener('click', fn), you are using the browser's native Event Emitter! (addEventListeneris essentially just.on()). - Node.js: The core of Node.js servers (listening for HTTP requests) is built entirely on a built-in
EventEmitterclass.
Watch Out: Memory Leaks!
One of the most important things a junior engineer needs
to learn about Event Emitters is why the .off() method
exists. If Alice leaves the chat but forgets to call
.off(), the Event Emitter will hold onto her listener
function forever. In frameworks like React, failing to
call .off() (or removeEventListener) when a component
unmounts is the #1 cause of memory leaks!