Prerequisite
Basic understanding of JavaScript and Node.js event-driven architecture.
If you have experience with JavaScript in browser, you're likely familiar with how user interactions, such as mouse clicks, keyboard inputs, and responding to mouse movements, are managed through events.
Similarly, Node.js provides the capability to create event-driven systems on the backend using its `events` module.
This module includes the `EventEmitter` class, which allows us to effectively manage and respond to events or state changes asynchronously in our applications.
Here's an overview of key concepts:
- ➢Basic Concepts: In event-driven programming, events are signals that something has happened. Event emitters are objects that `emit` events, and `listeners` are functions that subscribe to these events and respond to them. Event loops are central to managing and dispatching events in Node.js applications.
- ➢Event-Driven Architecture: This architecture promotes loose coupling, scalability, and responsiveness in applications. EventEmitter plays a crucial role in facilitating communication between different parts of an application through events.
Let's have a simple illustration:
// Import EventEmitter module
const EventEmitter = require('events');
// Create an instance of EventEmitter
const emitter = new EventEmitter();
// Register a listener for the 'greet' event
emitter.on('greet', () => {
console.log('Hello, world!');
});
// Emitting a 'greet' event
emitter.emit('greet'); // Output: Hello, world!
Breakdown: we create an EventEmitter instance `emitter`, define an event listener for the `greet` event using the `on` method, and emit the 'greet' event using the `emit` method.
When the event is emitted, the listener function is executed, printing 'Hello, world!'
to the console.
The EventEmitter provides methods to work with events, such as adding event listeners, emitting events, and handling errors. In this guide, we wouldn't get into the intricacies or cover all the public methods of the EventEmitter. If you're interested, you can read more on events.
Let's explore these functionalities in detail:
- ➢Adding Event Listeners: The `on` or `addListener` method is used to add event listeners to EventEmitter instances. Event listeners are functions that execute when a specific event is emitted.
- ➢Emitting Events: The `emit` method is used to emit events. When an event is emitted, all registered listeners for that event are invoked asynchronously.
- ➢Removing Event Listeners: The `off` or `removeListener` method can be used to remove event listeners from EventEmitter instances. This helps in avoiding memory leaks and unnecessary execution of listener functions.
Let's have an example that uses some popular methods:
// Importe EventEmitter module
const EventEmitter = require('events');
// Create an instance of EventEmitter
const emitter = new EventEmitter();
// Define event listeners
const listener1 = (msg) => console.log('Message received by listener1:', msg);
const listener2 = (msg) => console.log('Message received by listener2:', msg);
// register listeners
emitter.on('message', listener1);
//emitter.addListener('message', listener1);
emitter.on('message', listener2);
//emitter.addListener('message', listener2);
// Get the listener count for the 'message' event
console.log('Number of listeners for the message event: ', emitter.listenerCount('message'));
// Emitting events
emitter.emit('message', 'Hello, world!');
console.log('************ After removing listener1 **********************')
// Removing a listener (use either)
emitter.off('message', listener1);
//emitter.removeListener('message', listener1);
// Try emitting the 'message' event one more time
emitter.emit('message', 'Another Hello, world!');
// Get the listener count for the 'message' event
console.log('Upadted number of listeners for the message event: ', emitter.listenerCount('message'));
Breakdown: This example illustrates just how powerful and dynamic the nodejs events are.
- Run the code and see what you get in the console. The result are quite straight-forward and code is self-explanatory.
- Then for the event listener registering part, comment the active lines and uncomment the comment ones. Do the same for the listener removal part, then run the code again. As expected, you should get the exactly the same results.
- Also notice how emitter.once
registers `listener3` but then removes it immediately after it responds to the first emit call.
Let's explore some real-world scenarios where the Node.js EventEmitter shines.
1. Simple Chat Application
In a chat application, the EventEmitter can be used to handle incoming messages, user connections, and disconnections.
Each user connection can be represented as an EventEmitter instance, allowing the server to emit events for messages received from clients and broadcast them to other connected users.
const EventEmitter = require('events');
class ChatRoom extends EventEmitter {
constructor() {
super();
this.users = new Map();
}
addUser(user) {
this.users.set(user.id, user);
user.on('message', (message) => {
// Broadcast message to other users
this.emit('message', { userId: user.id, message });
});
user.on('disconnect', () => {
// Handle user disconnection
this.users.delete(user.id);
this.emit('userDisconnected', user.id);
});
this.emit('userConnected', user.id);
}
}
2. Event-Driven Microservices
In a microservices architecture, EventEmitter can facilitate communication between different `services`. Each microservice can emit events for various actions, such as user registration, order processing, or data synchronization. Other services can listen for these events and react accordingly.
// Service A
const EventEmitter = require('events');
const emitter = new EventEmitter();
// Service B
emitter.on('userRegistered', (user) => {
console.log(`New user registered: ${user}`);
});
// Service C
emitter.emit('userRegistered', { id: 123, name: 'John Doe' });
3. Web Server
In a web server application, EventEmitter can be used to handle HTTP requests and responses. The HTTP server instance in Node.js is an EventEmitter, allowing us developers to listen for incoming requests and respond accordingly.
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello World\n');
});
server.listen(3000, () => {
console.log('Server running on port 3000');
});
You're probably hoping for a runnable example, not like those provided above.
Gotcha! Let's have a more or less complete and runnable example.
const EventEmitter = require('events');
// ChatRoom class
class ChatRoom extends EventEmitter {
constructor() {
super();
this.users = new Map();
}
addUser(user) {
this.users.set(user.id, user);
user.on('message', (message) => {
// Broadcast message to other users
this.emit('message', { userId: user.id, message });
});
user.on('disconnect', () => {
// Handle user disconnection
this.users.delete(user.id);
this.emit('userDisconnected', user.id);
});
this.emit('userConnected', user.id);
}
sendMessage(userId, message) {
const user = this.users.get(userId);
if (user) {
user.emit('message', message);
}
}
}
// Setup User class
class User extends EventEmitter {
constructor(id) {
super();
this.id = id;
}
}
// Create a chatroom
const chatRoom = new ChatRoom();
// Create 2 users and add them to the chatroom
const user1 = new User(1);
const user2 = new User(2);
chatRoom.addUser(user1);
chatRoom.addUser(user2);
// Register a 'message' event listener in the chatroom
chatRoom.on('message', ({ userId, message }) => {
console.log(`User ${userId} says: ${message}`);
});
// Simulate sending messages by various users
setTimeout(() => {
chatRoom.sendMessage(1, 'Greetings from User 1!');
}, 1000);
setTimeout(() => {
chatRoom.sendMessage(2, 'Hola from User 2!');
}, 2000);
Breakdown:
- we have a `ChatRoom` class that extends `EventEmitter`, representing a chatroom where users can send and receive messages.
- Each `User` instance also extends `EventEmitter` and represents a user in the chatroom. We create two users (user1 and user2) and add them to the chatroom.
- Then, we simulate sending messages from each user, which are emitted as events and broadcasted to other users in the chatroom.
The example above is a contrived and servers just as an illustrative use case.
Conclusion
The Node.js EventEmitter is a powerful tool for building event-driven applications in nodeJS. By understanding its core concepts, API methods, and best practices, developers can create robust and scalable applications that leverage the event-driven architecture effectively.
In the next part, we'll dive into an important and sensitive topic in nodejs, Blocking the Event loop.
Chapter 1 , Part 1 : Introduction to NodeJS
In this series part, I introduce nodeJS and some technical concepts associated with it. I also show how easy it is to setup and start a simple nodeJS web server.
Chapter 1 , Part 2 : How to Install and Setup NodeJS
In this series part, I run you through the various ways to install nodeJS. I also discuss how to install nvm and use it to switch between different node versions.
Chapter 1 , Part 3 : How much JavaScript do you need to learn NodeJS
In this series part, we explore the nuanced relationship between JavaScript and NodeJS, highlighting some subtle distinctions between the two environments.
Chapter 1 , Part 4 : The v8 Engine and the difference Between NodeJS and the browser
In this series part, we explore the V8 engine and how it interacts with nodeJS. We also discuss node’s event loop and uncover the mystery behinds node’s ability to handle concurrent operations.
Chapter 1 , Part 5 : NPM, the NodeJS package manager
Discover the essentials of npm, the powerful package manager for Node.js. Learn installation, management, publishing, and best practices
Chapter 1 , Part 6 : NodeJS in Development Vs Production
Explore how Node.js behaves differently in development and production environments. Learn key considerations for deploying Node.js applications effectively.
Chapter 2 , Part 1 : Asynchronous Flow Control
In this series part, we'll explore various aspects of asynchronous flow control in Node.js, from basic concepts to advanced techniques.
Chapter 2 , Part 2 : Blocking vs Non-blocking I/O
Explore the differences between blocking and non-blocking I/O in Node.js, and learn how to optimize performance and scalability.
Chapter 2 , Part 3 : Understanding NodeJS Event loop
Exploring the Node.js event loop by understanding its phases, kernel integration, and processes enabling seamless handling of asynchronous operations in your applications.
Chapter 2 , Part 4 : The NodeJS EventEmitter
Explore the power of Node.js EventEmitter: an essential tool for building scalable and event-driven applications. Learn how to utilize it effectively!
Chapter 3 , Part 1 : Working with files in NodeJS
Gain comprehensive insights into file management in Node.js, covering file stats, paths, and descriptors, to streamline and enhance file operations in your applications.
Chapter 3 , Part 2 : Reading and Writing Files in NodeJS
Uncover the fundamentals of reading and writing files in nodeJS with comprehensive examples and use cases for some widely used methods.
Chapter 3 , Part 3 : Working with Folders in NodeJS
Unlock the secrets of folder manipulation in Node.js! Explore essential techniques and methods for working with directories efficiently
Chapter 4 , Part 1 : Running NodeJS Scripts
Master the command line interface for executing nodeJS scripts efficiently. Learn common options and best practices for seamless script execution
Chapter 4 , Part 2 : Reading Environment Variables in NodeJS
Learn how to efficiently manage environment variables in nodeJS applications. Explore various methods and best practices for security and portability
Chapter 4 , Part 3 : Writing Outputs to the Command Line in NodeJS
Learn essential techniques for writing outputs in nodeJS CLI. From basic logging to formatting and understanding stdout/stderr.
Chapter 4 , Part 4 : Reading Inputs from the Command Line in NodeJS
Learn the various ways and strategies to efficiently read command line inputs in nodeJS, making your program more interactive and flexible.
Chapter 4 , Part 5 : The NodeJS Read, Evaluate, Print, and Loop (REPL)
Explore the power of nodeJS's Read, Evaluate, Print, and Loop (REPL). Learn how to use this interactive environment for rapid prototyping, debugging, and experimentation.
Chapter 5 , Part 1 : Introduction to Testing in NodeJS
Discover the fundamentals of testing in nodeJS! Learn about testing types, frameworks, and best practices for building reliable applications.
Chapter 5 , Part 2 : Debugging Tools and Techniques in NodeJS
Explore essential debugging tools and techniques in Node.js development. From built-in options to advanced strategies, and best practices for effective debugging.
Chapter 6 , Part 1 : Project Planning and Setup
Discuss the planning and design process for building our interactive file explorer in Node.js, focusing on core features, UI/UX design, and implementation approach and initial setup.
Chapter 6 , Part 2 : Implementing Basic functionalities
In this guide, we'll implement the basic functionalities of our app which will cover initial welcome and action prompts.
Chapter 6 , Part 3 : Implementating Core Features and Conclusion
In this guide, we'll complete the rest of the more advanced functionalities of our app including, create, search, sort, delete, rename and navigate file directories.