Hello and welcome back to Code Review, a series of coding interview challenges and career related content released weekly exclusively on Dev.to. I’m Elisabeth and I’ve been a software engineer for about 4+ years now. I’m passionate about sharing my knowledge, and best tips and tricks when it comes to acing that interview and or just leveling up your coding skills. If you want more content and challenges like these, subscribe to the Coderbyte newsletter here. That’s it for stand up - let’s get to challenge solving!
The Challenge
Write a class, EventEmitter that has three methods: on, emit, and removeListener.
-
on("eventName", callbackFn)- a function that takes aneventNameand acallbackFn, should save the callbackFn to be called when the event witheventNameis emitted. -
emit("eventName", data)- a function that takes aneventNameanddataobject, should call thecallbackFns associated with that event and pass them thedataobject. -
removeListener("eventName", callbackFn)- a function that takeseventNameandcallbackFn, should remove thatcallbackFnfrom the event.
For example:
let superbowl = new EventEmitter()
const cheer = function (eventData) {
console.log('RAAAAAHHHH!!!! Go ' + eventData.scoringTeam)
}
const jeer = function (eventData) {
console.log('BOOOOOO ' + eventData.scoringTeam)
}
superbowl.on('touchdown', cheer)
superbowl.on('touchdown', jeer)
superbowl.emit('touchdown', { scoringTeam: 'Patriots' }) // Both cheer and jeer should have been called with data
superbowl.removeListener('touchdown', jeer)
superbowl.emit('touchdown', { scoringTeam: 'Seahawks' }); // Only cheer should have been called
The solution:
This is a great opportunity to use ES6 classes. In case you haven’t used them before, check out their syntax here. We can start with a basic structure for the class EventEmitter and initialize it with an object events that we will use to track our events.
class EventEmitter {
constructor () {
this.events = {}
}
}
On
Next we can start working on our methods. First up is on. Here is the code for that:
on (eventName, callbackFn) {
if (!this.events[eventName]) {
this.events[eventName] = []
}
this.events[eventName].push(callbackFn)
}
Because functions are first class objects in javascript, which basically means they can be stored in a variable, an object, or an array, we can just push the callback function to an array stored at the key eventName in our events object.
Emit
Now, for our emit function.
emit (eventName, eventData) {
if (!this.events[eventName]) return
this.events[eventName].forEach(fn => fn(eventData))
}
This solution takes advantage of what is called closure in javascript. If you are coding in Javascript in your interview, understanding closure can be vital. A closure is essentially when a function has references to its surrounding state or its lexical environment. You can also think of this as a closure allowing you access to an outer function’s scope from inside an inner function. Using global variables is a great simple example of closure.
Here’s another great example of using closure to track how many times a function was called.
function tracker (fn) {
let numTimesCalled = 0
return function () {
numTimesCalled++
console.log('I was called', numTimesCalled)
return fn()
}
}
function hello () {
console.log('hello')
}
const trackedHello = tracker(hello)
The inner function returned in tracker closes over the variable numTimesCalled and maintains a reference to it for the life of the trackedHello function. Cool stuff huh??
RemoveListener
The removeListener method is probably the easiest of the three. Here is a solution -
removeListener (eventName, callbackFn) {
const idx = this.events[eventName].indexOf(callbackFn)
if (idx === -1) return
this.events[eventName].splice(idx, 1)
}
And that’s the class! Pun intended :) Seeing if you can implement methods that are part of the language is a great way to practice for interviews. See you all next week!
Oldest comments (24)
That's a good question, and the solution is really clear👍
I'm not sure I'd call the
emitfunction a closure, it's not returning a function, and it's not being run outside it's lexical scope (although the callbacks are closures themselves I guess)Not the emit function - the event callbacks themself close over the
dataobject and gain access to that themselves!while I like the content, Google has not been a good place for developers for a while and we should stop idolizing the "anything related to google must be good" label
Couldn't agree more. Idolizing any company is a recipe for disappointment. However, they are KNOWN for the caliber of interview questions they ask so its really nice to see some real questions they've asked in the past!
Regardless of whatever you two decide is a better answer, its always nice to see other ways of solving the same problem! Play nicely :)
Very good post. Thanks.
Sorry to poop the party, but I hope google asks better questions in interviews for their sake.
Anyone smell that..? ;) Sometimes you get lucky with an "easier" problem - not the worst thing in the world!
That's not so different than a typical implementation:
github.com/Olical/EventEmitter/blo...
Kudos
how about doing a filter for removing a listener i.e filtering in objects which need not be removed.
Good point that will definitely be a bug! Nice catch!
PubSub
The bases for p/s, yes
Some comments may only be visible to logged-in visitors. Sign in to view all comments.