
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 cb2Example 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 subscriptionsExample 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 <= 10values.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, andunsubscribe. - The
EventEmitteraction doesn't take any arguments. - The
emitaction 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
subscribeaction takes 2 arguments, where the first one is the event name and the second is the callback function. - The
unsubscribeaction 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:
For every action of
EventEmitter:- Create an instance of the EventEmitter class, which will manage all the event handling logic.
For
subscribeactions:- 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.
For
emitactions:- 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.
For
unsubscribeactions:- 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
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
EventNotifierclass initializes with asubscribersobject that keeps track of events and their corresponding listeners (callbacks).Event Subscription (
onmethod):- Register a callback for a specified event type.
- If the event type does not already exist in the
subscribersobject, create a newSetcontaining the callback; otherwise, add the callback to the existing set for that event type. - Returns an object with a
detachmethod, allowing for the removal of the callback from the event type.
Event Notification (
notifymethod):- Trigger callbacks associated with a specified event type.
- If the event type does not exist in the
subscribersobject, return an empty array as there are no subscribers to notify. - 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.