Event Emitter

Updated on 30 May, 2025
Event Emitter header image

Problem Statement

In this task, you are required to design an EventEmitter class similar, yet distinct, from those used in Node.js and DOM's EventTarget interface. The primary purpose of this class is to facilitate the subscribing and emitting of events.

  • Subscribe: Users should be able to register interest in specific events by subscribing to them. Each subscription can involve multiple listeners and the responses should be managed in the order they were initially registered.
  • Emit: This function should enable the actual firing of an event, optionally passing arguments to the subscribed callbacks. If no subscriptions exist for an event, an empty response should be given.

This setup involves a straightforward interaction model where events can have multiple independent listeners, and upon triggering an event, every listener receives the appropriate data in a predictable sequence.

Examples

Example 1

Input:

actions = ["EventEmitter", "emit", "subscribe", "subscribe", "emit"],
values = [[], ["firstEvent"], ["firstEvent", "function cb1() { return 5; }"],  ["firstEvent", "function cb1() { return 6; }"], ["firstEvent"]]

Output:

[[],["emitted",[]],["subscribed"],["subscribed"],["emitted",[5,6]]]

Explanation:

const emitter = new EventEmitter();
emitter.emit("firstEvent"); // [], no callback are subscribed yet
emitter.subscribe("firstEvent", function cb1() { return 5; });
emitter.subscribe("firstEvent", function cb2() { return 6; });
emitter.emit("firstEvent"); // [5, 6], returns the output of cb1 and cb2

Example 2

Input:

actions = ["EventEmitter", "subscribe", "emit", "emit"],
values = [[], ["firstEvent", "function cb1(...args) { return args.join(','); }"], ["firstEvent", [1,2,3]], ["firstEvent", [3,4,6]]]

Output:

[[],["subscribed"],["emitted",["1,2,3"]],["emitted",["3,4,6"]]]

Explanation:

Note that the emit method should be able to accept an OPTIONAL array of arguments.
const emitter = new EventEmitter();
emitter.subscribe("firstEvent, function cb1(...args) { return args.join(','); });
emitter.emit("firstEvent", [1, 2, 3]); // ["1,2,3"]
emitter.emit("firstEvent", [3, 4, 6]); // ["3,4,6"]

Example 3

Input:

actions = ["EventEmitter", "subscribe", "emit", "unsubscribe", "emit"],
values = [[], ["firstEvent", "(...args) => args.join(',')"], ["firstEvent", [1,2,3]], [0], ["firstEvent", [4,5,6]]]

Output:

[[],["subscribed"],["emitted",["1,2,3"]],["unsubscribed",0],["emitted",[]]]

Explanation:

const emitter = new EventEmitter();
const sub = emitter.subscribe("firstEvent", (...args) => args.join(','));
emitter.emit("firstEvent", [1, 2, 3]); // ["1,2,3"]
sub.unsubscribe(); // undefined
emitter.emit("firstEvent", [4, 5, 6]); // [], there are no subscriptions

Example 4

Input:

actions = ["EventEmitter", "subscribe", "subscribe", "unsubscribe", "emit"],
values = [[], ["firstEvent", "x => x + 1"], ["firstEvent", "x => x + 2"], [0], ["firstEvent", [5]]]

Output:

[[],["subscribed"],["subscribed"],["unsubscribed",0],["emitted",[7]]]

Explanation:

const emitter = new EventEmitter();
const sub1 = emitter.subscribe("firstEvent", x => x + 1);
const sub2 = emitter.subscribe("firstEvent", x => x + 2);
sub1.unsubscribe(); // undefined
emitter.emit("firstEvent", [5]); // [7]

Constraints

  • 1 <= actions.length <= 10
  • values.length === actions.length
  • All test cases are valid, e.g. you don't need to handle scenarios when unsubscribing from a non-existing subscription.
  • There are only 4 different actions: EventEmitter, emit, subscribe, and unsubscribe.
  • The EventEmitter action doesn't take any arguments.
  • The emit action takes between either 1 or 2 arguments. The first argument is the name of the event we want to emit, and the 2nd argument is passed to the callback functions.
  • The subscribe action takes 2 arguments, where the first one is the event name and the second is the callback function.
  • The unsubscribe action takes one argument, which is the 0-indexed order of the subscription made before.

Approach and Intuition

To tackle this problem, let's break down the process into two main functionalities detailed in the examples:

  1. For every action of EventEmitter:

    • Create an instance of the EventEmitter class, which will manage all the event handling logic.
  2. For subscribe actions:

    • Record the subscription with the event name and associated callback.
    • Ensure that each callback can be unsubscribed individually without affecting other subscriptions to the same event.
  3. For emit actions:

    • When an event is triggered, collect all the responses from callbacks subscribed to this event. These should be processed in the same order as they were added.
    • If an event has arguments, pass them to each callback function.
    • Handle cases where no subscriptions are active for an event and return an empty array.
  4. For unsubscribe actions:

    • Allow a specific subscription (referenced by its position or a unique identifier) to be cancelled effectively, ensuring the internal lists of subscribers are updated accordingly.

By adhering to these principles, the EventEmitter class you design will mimic the functionality as described, processing and responding to internal events within your application or framework. These steps correspond to the intuitive process of observing and reacting to events, which is a staple in interactive, asynchronous programming environments.

Solutions

  • JavaScript
js
class EventNotifier {
  constructor() {
    this.subscribers = {};
  }

  on(eventType, callback) {
    if (!(eventType in this.subscribers)) {
      this.subscribers[eventType] = new Set([callback]);
    } else {
      this.subscribers[eventType].add(callback);
    }

    return {
      detach: () => {
        this.subscribers[eventType].delete(callback);
      },
    };
  }

  notify(eventType, parameters = []) {
    if (!(eventType in this.subscribers)) return [];
    const outcomes = [];
    this.subscribers[eventType].forEach((handler) => {
      outcomes.push(handler(...parameters));
    });
    return outcomes;
  }
}

The provided JavaScript code implements an EventNotifier class tailored for event handling. This class facilitates a subscription model where callbacks are registered to specific event types and can be notified when those events occur. Here's a breakdown of its functionalities:

  • Initialization: The EventNotifier class initializes with a subscribers object that keeps track of events and their corresponding listeners (callbacks).

  • Event Subscription (on method):

    1. Register a callback for a specified event type.
    2. If the event type does not already exist in the subscribers object, create a new Set containing the callback; otherwise, add the callback to the existing set for that event type.
    3. Returns an object with a detach method, allowing for the removal of the callback from the event type.
  • Event Notification (notify method):

    1. Trigger callbacks associated with a specified event type.
    2. If the event type does not exist in the subscribers object, return an empty array as there are no subscribers to notify.
    3. For each registered callback, execute the callback passing any provided parameters and collect the return values into an array.

This system is efficient for scenarios where components or services within an application require notification of events to perform actions, acting as a core mechanism for implementing observer patterns in applications.

Comments

No comments yet.