Understanding inter-module communication
We've found out how to put our code logic into modules. Now, we need to know how to make them communicate with each other. Very often, people describe Node.js as an event-driven system. It's also called non-blocking because as we have seen earlier in the chapter, it can accept a new request even before the previous request is fully complete. That's very efficient and highly scalable. The events are very powerful and are good means to inform the other modules of what is going on. They bring about encapsulation, which is very important in modular programming. Let's add some events to the car example we discussed earlier. Let's say that we have air conditioning, and we need to know when it is started. The implementation of such logic consists of two parts. The first one is the air conditioning module. It should dispatch an event that indicates the start of the action. The second part is the other code that listens for that event. We will create a new file called air.js
containing the logic responsible for the air conditioning, as follows:
// air.js var util = require("util"); var EventEmitter = require('events').EventEmitter; var Class = function() { } util.inherits(Class, EventEmitter); Class.prototype.start = function() { this.emit("started"); }; module.exports = Class;
Our class extends a Node.js module called EventEmitter
. It contains methods such as emit
or on
, which help us to establish event-based communication. There is only one custom method defined: start
. It simply dispatches an event that indicates that the air conditioning is turned on. The following code shows how we can attach a listener:
// car.js var AirConditioning = require("./air.js"); var air = new AirConditioning(); air.on("started", function() { console.log("Air conditioning started"); }); air.start();
A new instance of the AirConditioning
class is created. We attached an event listener and fired the start
method. The handler is called, and the message is printed to the console. The example is a simple one but shows how two modules communicate. It's a really powerful approach because it offers encapsulation. The module knows its responsibilities and is not interested in the operations in the other parts of the system. It simply does its job and dispatches notifications (events). For example, in the previous code, the AirConditioning
class doesn't know that we will output a message when it is started. It only knows that one particular event should be dispatched.
Very often, we need to send data during the emitting of an event. This is really easy. We just have to pass another parameter along with the name of the event. Here is how we send a status
property:
Class.prototype.start = function() { this.emit("started", { status: "cold" }); };
The object attached to the event contains some information about the air conditioning module. The same object will be available in the listener of the event. The following code shows us how to get the value of the status
variable mentioned previously:
air.on("started", function(data) { console.log("Status: " + data.status); });
There is a design pattern that illustrates the preceding process. It's called the Observer. In the context of that pattern, our air conditioning module is called subject, and the car module is called the observer. The subject broadcasts messages or events to its observers, notifying them that something has changed.
If we need to remove a listener, Node.js has a method for that called removeListener
. We can even allow a specific number of observers using setMaxListeners
. Overall, the events are one of the best ways to wire your logical parts. The main benefit is that you isolate the module, but it is still highly communicative with the rest of your application.