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:
- Understand the basics of how Event Emitters work.
- Start implementing your own simple Event Emitter in TypeScript.
- Learn how to write and run unit tests to make sure your code is working as expected.
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:
m.subscribe("mock event", (n: number) => val = n);: We’re telling our EventEmitter to listen for an event called "mock event". When that event happens, we want to run the function that sets val to whatever value is passed to the event.
m.emit("mock event", 18);: We’re emitting the "mock event" and passing the value 18 to it.
expect(val).toBe(18);: We’re checking if val got updated to 18 as we expected.
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?
- We’re adding a new listener (
cb) to the array of listeners for the event name.
- If the event doesn’t exist yet, we create an empty array for it.
- The
release function lets us unsubscribe from the event later by removing the listener from the array.
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:
- The
emit method looks up the event by its name.
- It loops through all the listeners for that event and calls each one, passing any arguments (
...args) along.
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:
- Learned what an Event Emitter is and why it’s useful.
- Set up an empty
EventEmitter class.
- Wrote a test to define the desired behavior.
- Implemented basic
subscribe and emit methods.
- Ran your test to confirm everything works as expected.
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!