Medium
Design an EventEmitter
class. This interface is similar (but with some differences) to the one found in Node.js or the Event Target interface of the DOM. The EventEmitter
should allow for subscribing to events and emitting them.
Your EventEmitter
class should have the following two methods:
subscribe
are referentially identical. subscribe
method should also return an object with an unsubscribe
method that enables the user to unsubscribe. When it is called, the callback should be removed from the list of subscriptions and undefined
should be returned.Example 1:
Input: actions = [“EventEmitter”, “emit”, “subscribe”, “subscribe”, “emit”], values = [[], [“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”],[“emitted”,[“1,2,3”]],[“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
EventEmitter
, emit
, subscribe
, and unsubscribe
.EventEmitter
action doesn’t take any arguments.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.subscribe
action takes 2 arguments, where the first one is the event name and the second is the callback function.unsubscribe
action takes one argument, which is the 0-indexed order of the subscription made before.type Callback = (...args: any[]) => any
type Subscription = {
unsubscribe: () => void
}
class EventEmitter {
eventMap: Map<string, Set<Callback>>
constructor() {
this.eventMap = new Map()
}
subscribe(eventName: string, callback: Callback): Subscription {
if (this.eventMap.has(eventName)) {
const set = this.eventMap.get(eventName)!
set.add(callback)
this.eventMap.set(eventName, set)
} else {
const set = new Set<Callback>()
set.add(callback)
this.eventMap.set(eventName, set)
}
return {
unsubscribe: () => {
this.eventMap.get(eventName).delete(callback)
},
}
}
emit(eventName: string, args: any[] = []): any[] {
const res = []
this.eventMap.get(eventName)?.forEach((cb) => res.push(cb(...args)))
return res
}
}
/*
* const emitter = new EventEmitter();
*
* // Subscribe to the onClick event with onClickCallback
* function onClickCallback() { return 99 }
* const sub = emitter.subscribe('onClick', onClickCallback);
*
* emitter.emit('onClick'); // [99]
* sub.unsubscribe(); // undefined
* emitter.emit('onClick'); // []
*/
export { EventEmitter }