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.
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:
- Download Node.js to run JavaScript on your computer
- Use your browser's Developer Console (Press F12) to test code snippets
- Create a .html file with <script> tags and open it in your browser
What You'll Learn in This Lesson
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:
- You have a Subject (also called Observable or Emitter)
- You have many Observers (also called Listeners or Subscribers)
The Subject keeps a list of observers and notifies them when something important happens.
- The Subject is often an object with methods like .on() , .off() , .emit()
- The Observers are callback functions you register
"When X happens, run all the functions that are interested in X."
- DOM: button.addEventListener("click", handler)
- Node.js: emitter.on("data", handler)
- React: custom hooks or global event buses
- Game engines: health changes, score updates, level load events
- Chat apps: message received, user typing, connection lost
Building a Very Simple Custom Event Emitter
Here's a minimal implementation you can paste into your editor:
- this.events is a map from eventName → array of callback functions
- .on() registers a listener
- .emit() triggers all listeners for that event
- Add two greet listeners and see both get called
- Create another event like "order:created"
Why Not Just Call Functions Directly?
- Checkout module
- Email module
- Analytics module
- Inventory module
When an order is created, you could call functions like:
This quickly becomes messy, tightly coupled, and painful to extend.
- The checkout module just emits order:created
- Any module that cares subscribes to order:created
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:
- First time user logs in
- First time connection succeeds
- Only the first successful payment
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
- Memory leaks
- Same handler firing 20 times later
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:
- Custom analytics system – emit "page:viewed", "button:clicked" and log/persist
- Game frameworks – "player:died", "score:updated", "enemy:spawned"
- Chat systems – "message:received", "user:joined", "typing:start"
- Financial dashboards – "price:update", "portfolio:refresh"
- AI tools – "job:queued", "job:completed", "token:usage:update"
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:
- Cleaner architecture
- Clear mental model
- Avoids collisions
- Better debugging
- Lets you group features
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:
- Listener 1 → validates order
- Listener 2 → charges payment
- Listener 3 → sends email
The charging listener should not run before validation.
🔥 Wildcard Event Matching (user:* Listeners)
Frameworks like Socket.IO and Vue.js use wildcard events.
- "user:login" listeners
- "user:*" listeners
Add "user:logout" and watch your wildcard handler still catch it.
🔥 Async Event Emitters (Wait for Listeners)
- Queue systems
- Payment pipelines
- Database migrations
- AI job processing
- Worker pools
Perfect for pipelines where stages must finish in sequence.
🔥 Mistakes to Avoid
- ❌ Accidentally creating infinite loops: emitter.on("update", () => emitter.emit("update"));
- ❌ Emitting too often (UI re-renders a thousand times)
- ❌ Storing huge payloads (memory leaks)
- ❌ Forgetting to off() listeners
- ❌ Using events when simple function calls would do
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)
- HTTP requests
- File system watchers
✔️ 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
- payment:authorized
- payment:captured
- invoice:paid
- refund:issued
- risk:flagged
Each event triggers multiple internal services.
✔️ Discord, Slack, Real-Time Chat Apps
Real-time messaging relies on event buses for:
- message received
✔️ 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.
- Scroll events
- Search inputs
- API spam protection
Challenge 3 — Record All Events
Challenge 4 — Async Ordered Pipeline
Interactive Code Editor
Try out the concepts you've learned. The editor below has examples you can run and modify:
📌 Final Summary
Custom Event Emitters and the Observer Pattern are the backbone of many of the world's largest systems. By mastering event architecture — namespaced events, priorities, async flows, debugging tools, memory management, pipelines, and modular design — developers gain the ability to structure applications that are scalable, reactive, and easy to extend. This lesson gives you the same foundation used in React, Node.js, Stripe, Discord, and major game engines, preparing you for real-world engineering and professional JavaScript architecture.
📋 Quick Reference
Method
Purpose
.on(event, fn)
Register a listener for an event
.emit(event, ...args)
Trigger all listeners for an event
.off(event, fn)
Remove a specific listener
.once(event, fn)
Listen once, then auto-unsubscribe
"user:login"
Namespaced event pattern
AsyncEmitter
Await all listeners in sequence
Lesson Complete!
You've built a production-grade event emitter from scratch, implemented on/off/once/priority/async patterns, and learned the architecture behind Node.js, React, Stripe, and Discord.
Up next: Debouncing & Throttling — control high-frequency events for smooth, performant UIs.
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.