Building an EventEmitter in TypeScript: Solving the 30-Days-JavaScript LeetCode Challenge

Elijah Koulaxis

June 26, 2023

EventEmitter

Exams are finally over! That's why I decided to take up the challenge of the 30-Days-JavaScript LeetCode challenge. One problem that caught my attention was designing an EventEmitter class. In this post, we'll dive into the problem statement, explore my solution, and see how it can be used in practical scenarios! I personally believe that this would be an amazing coding interview question.

Highly recommend you to solve these questions: 30-days-of-javascript-leetcode

Understanding the problem

The task at hand was to implement an EventEmitter class with two essential methods: subscribe and emit. The subscribe method allows us to register callback functions for specific events, while the emit method triggers the registered callbacks when an event occurs.

Leetcode Question

Designing the EventEmitter Class

type Callback = (...args: any[]) => any;
type Subscription = {
    unsubscribe: () => void;
};

class EventEmitter {
    private events: Map<string, Callback[]> = new Map();

    subscribe(eventName, callback) {
        if (!this.events.has(eventName)) {
            this.events.set(eventName, []);
        }

        const event = this.events.get(eventName);
        event.push(callback);

        return {
            unsubscribe: () => {
                const index = event.indexOf(callback);

                if (index > -1) {
                    event.splice(index, 1);
                }
            },
        };
    }

    emit(eventName, args = []) {
        const event = this.events.get(eventName);

        if (!event) {
            return [];
        }

        return event.map(callback => callback(...args));
    }
}

The EventEmitter class contains a Map called events to store the subscribed callbacks for each event name. The subscribe method checks if the event already exists in the map and creates an empty array if not. It then adds the new callback to the event's array of callbacks. The method returns an object with an unsubscribe method that removes the callback from the event's array of callbacks when called.

The emit method triggers the callbacks associated with a specific event. It retrieves the event's array of callbacks from the events map and invokes each callback with the provided arguments (or an empty array). The results of the callback invocations are returned as an array.

Practical Use Cases

Where could we use an EventEmitter?

  1. User Interaction Events

const eventEmitter = new EventEmitter();

const clickSubscription = eventEmitter.subscribe('click', () => {
    console.log('User clicked on the button');
});

// Trigger the 'click' event
eventEmitter.emit('click');
  1. Data Loading Events

const eventEmitter = new EventEmitter();

const dataLoadedSubscription = eventEmitter.subscribe('dataLoaded', (data) => {
    console.log('Data loaded:', data);
});

const data = fetchData(); // Simulate data loading 
eventEmitter.emit('dataLoaded', data);
  1. Custom Event Management

const eventEmitter = new EventEmitter();

const subscription = eventEmitter.subscribe('customEvent', (arg1, arg2) => {
    console.log('Custom event emitted with arguments:', arg1, arg2);
});

// Emit the custom event with arguments
eventEmitter.emit('customEvent', 'Hello', 'World');

Conclusion

With the EventEmitter class in the power of our hands, we can handle user interactions, manage data loading events, and create custom event-driven systems. It opens up a world of possibilities!

I hope you found this post helpful in understanding event handling and building an EventEmitter. Now, go forth and add some event-driven magic to your JavaScript projects!

Here's the github repo with all the solutions of this challenge

Happy coding!

Tags:
Back to Home