MediumLinkedInAirbnbGoogleLyftTikTokMicrosoftPaypal

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 a listener function to a specific eventName. Multiple listeners can be subscribed to the same event.
  • off(eventName, listener): Unsubscribes a specific listener function from the eventName. If the listener isn't subscribed, it should do nothing.
  • emit(eventName, ...args): Triggers all listeners subscribed to the eventName in the order they were added, passing any provided args to 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

Hint 1

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.

Hint 2

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.

Hint 3

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.

Hint 4

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!

  • on is like subscribing to the group chat.
  • off is like leaving the chat (unsubscribing).
  • emit is 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.

  1. First, we check if 'newMessage' already exists in our this.events object. If it doesn't, we create an empty array for it.
  2. 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.

  1. We look up 'newMessage' in our object.
  2. 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 not aliceListener. 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!').

  1. We look up 'newMessage' in our object.
  2. If we find an array of functions (like Bob and Alice's listeners), we loop through it using .forEach().
  3. 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! (addEventListener is essentially just .on()).
  • Node.js: The core of Node.js servers (listening for HTTP requests) is built entirely on a built-in EventEmitter class.

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!