Events Overview
Another way to invoke asynchronous non-blocking code is by using Event model, apart from the callback functions. Event model follows a publish and subscribe approach.
You can have multiple functions subscribed to a published event, so that when that event occurs all those functions that have subscribed get executed automatically.
EventEmitter Class
Node.js provides EventEmitter class which we can use to build event driven interfaces. This class is present within events module.
Subscribing for an Event
The code that wants to get subscribed for events, will be passed into the “on
” function of the EventEmitter
instance, along with the name of the event.
EventEmitter.on(event_name, listener);
Publishing an Event
The code that publishes the events call emit function and specifies the name of the event that is being emitted.
EventEmitter.emit(event_name, [arguments]);
The first argument of emit function is the name of the event. We can call the emit function with zero or more arguments right after event name. These arguments will be passed as parameters to the functions that have subscribed for this event.
Working with EventEmitter
There are 3 different ways in which you can publish and subscribe for events using EventEmitter
.
- Directly creating an instance of
EventEmitter
- Inheriting the prototype methods of
EventEmitter
usingutil.inherits
- Inheriting
EventEmitter
using ES6 class and extends keywords
Instantiating EventEmitter
In this approach, we create an instance of EventEmitter
in a function. This function publishes 3 events by emitting events at logical endpoints of the program.
It emits “start” event when the function begins execution, “fetch” event for each user record and “end” event after completion of fetching the records. You can see that when it emits “fetch” event it sends the record as the second argument to emit function.
At the end of the function, it returns EventEmitter
object. We use this object to subscribe event listeners using the “on” methods. The “fetch” listener receives the published user record.
var EventEmitter = require("events").EventEmitter;
var getUsers = function() {
let emitter = new EventEmitter();
emitter.emit("start");
process.nextTick(function() {
let mockUsers = [
["U001", "John Doe", "john.doe@techstackjournal.com"],
["U002", "Jane Doe", "jane.doe@techstackjournal.com"]
];
mockUsers.forEach(user => emitter.emit("fetch", user));
emitter.emit("end");
});
return emitter;
};
var emitter = getUsers();
emitter.on("start", function() {
console.log("getUsers Started");
});
emitter.on("fetch", function(user) {
console.log(user);
});
emitter.on("end", function(t) {
console.log("getUsers Completed.");
});
console.log("Event and listeners initialized...");
In this example, we’ve used process.nextTick
to emulate the asynchronous operation. On the very next tick of the Event Loop, it executes this function. That means Event Loop executes this function only after executing the last statement in our program.
Inheriting EventEmitter using util.inherits
In this approach, we inherit the prototype methods of EventEmitter
into UserService
using util.inherits
. We then use the instance of UserService
to subscribe for events. Within UserService
we emit events using the reference of this operator as if UserService
is an EventEmitter
even before UserService
inherits prototype methods of EventEmitter
using util.inherits
method.
var EventEmitter = require("events").EventEmitter;
var util = require("util");
var UserService = function() {
let self = this;
process.nextTick(function() {
let mockUsers = [
["U001", "John Doe", "john.doe@techstackjournal.com"],
["U002", "Jane Doe", "jane.doe@techstackjournal.com"]
];
self.emit("start");
mockUsers.forEach(user => self.emit("fetch", user));
self.emit("end");
});
};
util.inherits(UserService, EventEmitter);
var emitter = new UserService();
emitter.on("start", function() {
console.log("getUsers Started");
});
emitter.on("fetch", function(user) {
console.log(user);
});
emitter.on("end", function(t) {
console.log("getUsers Completed.");
});
console.log("Event and listeners initialized...");
Inheriting EventEmitter using ES6 Class
In this approach, we’ll create an ES6 class extending from EventEmitter
. Within the ES6 class we emit events using the reference of this operator. We create an instance of that ES6 class to subscribe for the events.
const EventEmitter = require("events");
class UserService extends EventEmitter {
traverse() {
let self = this;
process.nextTick(function(){
let mockUsers = [
["U001", "John Doe", "john.doe@techstackjournal.com"],
["U002", "Jane Doe", "jane.doe@techstackjournal.com"]
];
self.emit("start");
mockUsers.forEach(user => self.emit("fetch", user));
self.emit("end");
});
}
}
const service = new UserService();
service.on("start", function() {
console.log("traverse started...");
});
service.on("fetch", function(user) {
console.log(user);
});
service.on("end", function() {
console.log("traverse ends...");
});
service.traverse();
console.log("Event and listeners initialized...");
Built-in Classes that Inherit EventEmitter
Certain built-in classes in Node.js emit events based on the actions or its state. For example, http.Server
class inherits from EventEmitter
and it emits 'request'
event. Similarly, fs.ReadStream
class also inherits from EventEmitter
and emits 'data'
event.
Simple Web Application using http.Server
Let’s see a simple example of http.Server
which extends EventEmitter
. We first create an instance of http.Server
using http.createServer
function. We call the listen method on this server object to listen for server requests on port 8080.
language-jsvar http = require("http");
var server = http.createServer();
server.on("request", (request, response) => {
response.end("Good Morning!!!");
console.log(request.url);
});
server.on("close", () => {
console.log("Bye!!!");
});
process.on("SIGINT", () => {
server.close();
});
server.listen(8080);
console.log("Server started. Hit http://localhost:8080");
We then subscribe for server events request
and close
. When the request
event emits, it will pass on the request
and response
objects to its listeners or subscribers. Using response.end
method we are returning a “Good Morning” message to the browser. Event Loop will raise close
event on server when the server goes down. Our subscribed listener listen the close
event and logs “Bye!!!”.
We are also subscribing for “SIGINT
” event on process
object. When we press Ctrl+C to terminate the server, a “SIGINT
” signal will be raised. When SIGINT
signal is raised, we are calling server.close
to kill the server.