Cohort

Event Emitters

Looking how to make JavaScript subscribable

Welcome to Your First Lesson!

Hey there! 🌟 Welcome to the start of your journey into the world of Event Emitters in TypeScript. Whether you’re new to this concept or just looking to sharpen your skills, you’re in the right place. Let’s dive in together!

What’s an Event Emitter Anyway?

Imagine you’re at a concert. The band is playing (emitting) music, and the audience is listening (subscribing) to it. If the band suddenly stops playing, the audience stops hearing the music. An Event Emitter in TypeScript works similarly. It’s a pattern that lets objects “emit” events and other parts of your code “listen” or respond to them.

This pattern is super handy when you want your code to respond to things like user actions, data updates, or system events—without having everything tangled up together.

Our Goal

By the end of this lesson, you’ll:

Ready? Let’s go!

Step 1: Start with an Empty Slate

We’re going to start by writing a very basic EventEmitter class. Don’t worry, it’s simpler than it sounds! To kick things off, let’s set up our TypeScript class, but keep it empty for now. Here’s what it looks like:

export type EmitterEventsType = Record<string, Function[]>;

export class EventEmitter {
	public events: EmitterEventsType;

	constructor(events?: EmitterEventsType) {
		this.events = events || {};
	}
}

All we’ve done here is create a class called EventEmitter with a constructor that takes an optional events object. If no events object is provided, we initialize it as an empty object. This is where we’ll store our events and their listeners.

Step 2: Write Our First Test

Now, before we add any functionality to our EventEmitter, let’s write a test to describe what we want it to do. Writing tests first (a practice known as Test-Driven Development, or TDD) helps us focus on the exact behavior we need and keeps our code clean.

Let’s write a test to check if we can emit an event and have a listener respond to it:

import { describe, expect, test } from "bun:test";
import { EventEmitter } from "./event-emitter";

describe("Event Emitter", () => {
	test("emits result when fired", () => {
		const m = new EventEmitter({});
		let val = 0;

		m.subscribe("mock event", (n: number) => (val = n));
		m.emit("mock event", 18);

		expect(val).toBe(18);
	});
});

Here’s what’s happening:

Step 3: Implement the subscribe Method

Now that we have a test in place, let’s make it pass! We need to implement the subscribe method in our EventEmitter class so it can register event listeners:

subscribe(name: string, cb: Function) {
    (this.events[name] || (this.events[name] = [])).push(cb);
    return {
        release: () => this.events[name]?.splice(this.events[name].indexOf(cb), 1),
    };
}

What’s going on here?

Step 4: Implement the emit Method

Next up, we need our emit method, which will trigger all the listeners for a given event:

emit(name: string, ...args: unknown[]): void {
    for (const fn of this.events[name] || []) {
        fn(...args);
    }
}

Here’s how it works:

Step 5: Run the Test

With both subscribe and emit implemented, let’s run our test. If everything is set up correctly, you should see that the test passes! 🎉 This means your EventEmitter can now register listeners and emit events successfully.

What’s Next?

Awesome job! You’ve just built a basic Event Emitter in TypeScript. In the next lesson, we’ll dive deeper into more complex features, like unsubscribing from events and handling multiple listeners.

Until then, feel free to play around with your EventEmitter and think of other scenarios where you could use it. Practice makes perfect, and the more you experiment, the more comfortable you’ll become with this powerful pattern.

See you in the next lesson! đź‘‹


Summary

In this lesson, you:

Keep up the great work, and let’s continue building on this foundation!


Feel free to use this markdown as the basis for your lesson. Let me know if you need any more help!