Event Emitters
In real JavaScript apps, you often need different parts of your code to "react" when something happens somewhere else. A user clicks a button, data finishes loading, a WebSocket receives a message, a game character takes damage, a payment succeeds, a timer ends, or an AI response arrives. You don't want everything tightly glued together with messy function calls. Instead, you want a clean, decoupled system where one part emits events and other parts subscribe and react.
Learn Python, JavaScript, Java and more with free interactive lessons, real projects and AI-powered help. Beginner-friendly.
Part of the free JavaScript course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
💡 Running Code Locally: While this online editor runs real JavaScript, some advanced examples may have limitations. For the best experience:
That is exactly what custom event emitters and the Observer Pattern give you: a flexible way to broadcast events and let many listeners respond without tightly coupling modules.
What is the Observer Pattern?
The Observer Pattern is a design pattern where:
The Subject keeps a list of observers and notifies them when something important happens.
"When X happens, run all the functions that are interested in X."
Building a Very Simple Custom Event Emitter
Here's a minimal implementation you can paste into your editor:
Why Not Just Call Functions Directly?
When an order is created, you could call functions like:
This quickly becomes messy, tightly coupled, and painful to extend.
Now createOrder has no idea who is listening. You can add or remove observers without touching it.
Adding .off() to Unsubscribe
A robust event emitter lets you remove listeners too.
Returning an unsubscribe function from .on() is a very common pattern in modern JS (React hooks, Redux, custom hooks).
It keeps memory usage under control and prevents old components from still responding when they're "gone".
One-Time Events with .once()
Sometimes you want a listener to run only once — for example:
This is a powerful pattern when building initialization flows and onboarding experiences.
Common Mistakes with Custom Event Emitters
Here are very realistic bugs developers make that you should avoid:
1. Forgetting to clean up listeners
2. Misspelling event names
Nothing happens, no errors, silently fails. A good pattern is to define event name constants.
3. Using event emitter instead of simple function calls
Don't use an event emitter for simple internal function calls inside the same small module. It can add unnecessary complexity. Use events when you really need decoupling between parts of the app.
4. Passing too many arguments
If your event passes 8 parameters, it's usually better to pass a single object:
Real-World Use Cases Where This Shines
You can use custom event emitters and the Observer Pattern in:
This is not "toy JavaScript." This is the architecture behind reactive, scalable apps.
🔥 Namespaced Events (Professional-Grade Structure)
When building large JavaScript apps, you should avoid dumping everything into generic event names like "data" or "update". Instead, define namespaces:
Rename the event, add a second listener, or group multiple events under "user:*".
🔥 Priority Listeners (Run Important Handlers First)
Sometimes one listener MUST run before others. Example:
The charging listener should not run before validation.
🔥 Wildcard Event Matching (user:* Listeners)
Frameworks like Socket.IO and Vue.js use wildcard events.
Add "user:logout" and watch your wildcard handler still catch it.
🔥 Async Event Emitters (Wait for Listeners)
Perfect for pipelines where stages must finish in sequence.
🔥 Mistakes to Avoid
Don't overuse them — use them when decoupling is necessary.
🚀 Real-World Uses of Observer Pattern
Custom event emitters aren't just "an advanced JS trick." They are the foundation of huge, real-world systems:
✔️ Node.js core (EventEmitter class)
✔️ React & Vue (Reactivity Layer)
Internally rely on dependency tracking systems that follow the Observer Pattern.
A change triggers watchers → watchers update UI.
✔️ Stripe, PayPal, Banking Systems
Each event triggers multiple internal services.
✔️ Discord, Slack, Real-Time Chat Apps
Real-time messaging relies on event buses for:
✔️ Game Engines
Unity, Godot, Roblox, and JS-based engines all use event dispatchers.
🧪 Practice Challenges
Challenge 1 — Build a Todo Bus
Challenge 2 — Throttled EventEmitter
Create an emitter that blocks repeated events if fired too fast.
Practice quiz
In the Observer Pattern, what is the Subject also called?
- Listener
- Callback
- Observable or Emitter
- Handler
Answer: Observable or Emitter. The Subject (also called Observable or Emitter) keeps a list of observers and notifies them when something happens.
In the custom EventEmitter, what does this.events store?
- A map from eventName to an array of callback functions
- A single callback function
- An array of event names only
- The most recent event payload
Answer: A map from eventName to an array of callback functions. this.events is a map from eventName to an array of listener callbacks registered for that event.
What does the .on() method do?
- Triggers all listeners
- Removes a listener
- Clears all events
- Registers a listener for an event
Answer: Registers a listener for an event. .on() registers a listener by pushing the callback onto the array for that event name.
What does .emit(eventName, ...args) do?
- Registers a new listener
- Triggers all listeners for that event, passing them the args
- Deletes the event
- Returns the listener count
Answer: Triggers all listeners for that event, passing them the args. .emit() triggers all listeners for the event, calling each with the forwarded arguments.
What is the main benefit of using an event emitter over calling functions directly?
- It decouples modules so the emitter doesn't know who is listening
- It runs code faster
- It removes the need for callbacks
- It prevents all bugs
Answer: It decouples modules so the emitter doesn't know who is listening. With events, createOrder just emits 'order:created' and has no idea who listens, so you add/remove observers without touching it.
What is a common, useful pattern returned by the .on() method in the robust emitter?
- The event name
- The listener count
- An unsubscribe function
- A Promise
Answer: An unsubscribe function. .on() returns an unsubscribe function (() => this.off(...)), a common pattern in React hooks and Redux.
What does .once() do?
- Runs a listener exactly once per second
- Runs a listener only one time, then auto-unsubscribes
- Registers a listener that never fires
- Removes all listeners
Answer: Runs a listener only one time, then auto-unsubscribes. .once() wraps the listener so it runs once and then calls off() to remove itself.
In the priority example, listeners sort with (a, b) => b.priority - a.priority. What runs first?
- The lowest priority listener
- The most recently added listener
- They run in random order
- The highest priority listener
Answer: The highest priority listener. Sorting by b.priority - a.priority puts higher priority first, so Validate (10) runs before Charge (5) and Send email (0).
Which is listed as a common mistake with event emitters?
- Using namespaced event names
- Forgetting to clean up (off) listeners, causing memory leaks
- Passing a single object payload
- Returning an unsubscribe function
Answer: Forgetting to clean up (off) listeners, causing memory leaks. Forgetting to off() listeners leads to memory leaks and the same handler firing many times later.
Why does the lesson recommend namespaced event names like 'user:login'?
- They run faster
- They use less memory
- They give cleaner architecture, avoid collisions, and group features
- They are required by JavaScript
Answer: They give cleaner architecture, avoid collisions, and group features. Namespaced events (user:login, cart:item:add) provide cleaner architecture, avoid name collisions, and let you group features.