Decoupling Logic: The Power of Custom EventEmitters
In Node.js, the Observer Pattern is baked into the core. While you might use `fs` or `http` modules, under the hood, they are all instances of EventEmitter. Learning to create your own custom emitters is the step that separates a junior developer from a backend architect.
The Architecture of Decoupling
Imagine a user registration system. A monolithic approach puts database logic, email sending, and analytics tracking all in one function.
❌ Monolithic/Tightly Coupled
function register(user) {
db.save(user);
email.sendWelcome(user);
analytics.track(user);
}Hard to test. If email fails, does registration fail?
✔️ Event Driven/Decoupled
class User extends EventEmitter {
register(user) {
db.save(user);
this.emit('registered', user);
}
}Listeners handle side effects independently.
Handling Errors: The Golden Rule
In Node.js, the string `'error'` is special. If an EventEmitter emits an event named `'error'` and there are no listeners attached to it, Node.js will throw an exception, print a stack trace, and crash the process.
Critical: Always attach an `.on('error', cb)` listener to your custom emitters, or wrap your emit calls in try/catch blocks if using async/await wrappers.
Memory Leaks
Every time you call `.on()`, you add a function to an array in memory. If you add listeners in a loop or on repeated requests without removing them (using `.off()` or `.removeListener()`), your application's memory usage will grow indefinitely until it crashes.