In this lesson, you will learn how to use an EventEmitter to resolve dependencies between modules without them needing to import each other directly.
The Problem: Tight Coupling
Usually, in Dependency Injection (DI), we pass a service directly into a constructor: new DatabaseService(config). However, if your application is highly modular or asynchronous, your services might not be ready at the exact moment you need them. If Module A requires Module B, but Module B is still initializing, you end up with undefined errors or complex timing issues.
The Solution: Event-Driven Resolution
Instead of passing dependencies manually, we use an EventEmitter as a central "Registry." Modules declare what they need by listening for specific events, and the Provider modules emit those dependencies once they are ready. This decouples the timing of the dependency from the definition of the dependency.
Imagine a Database module that needs to signal to the rest of the app that it is connected:
const EventEmitter = require('events');
const registry = new EventEmitter();
// The Provider: Announces availability
const db = { query: (q) => console.log(`Executing: ${q}`) };
setTimeout(() => {
registry.emit('db-ready', db);
}, 1000);
// The Consumer: Waits for the dependency to exist
registry.once('db-ready', (database) => {
database.query('SELECT * FROM users');
});
Why use this?
- Asynchronous Readiness: You don't care when the database starts; your consumer will simply trigger whenever the
db-readyevent fires. - Reduced Circular Dependencies: Since modules don't
requireeach other directly to get their dependencies, you avoid the messy "circular dependency" errors common in large Node.js projects. - Flexibility: You can swap the
Databasemodule for aMockDatabasemodule during testing without changing a single line of code in the consumer—you only change which module emits thedb-readyevent.
By shifting from "passing" dependencies to "broadcasting" them, your architecture becomes much more resilient to the asynchronous nature of Node.js.
Key Takeaway: Event-Driven Dependency Injection allows modules to remain decoupled by using an Event Emitter as a central bus where providers broadcast their readiness and consumers wait for their required services to arrive.