In this tutorial we will go over a core concept in NodeJS and that is the event loop. You will probably be already familiar with events if you have written client side Javascript. By and large the principles are very much the same with a few distinctions in behaviour and terminology. Before we begin I'd like to draw your attention to the NodeJS description on their website:

Node.js is a JavaScript runtime built on Chrome's V8 JavaScript engine. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient.

The important bit to note here is that NodeJS is event based and this is the way we should construct our code to best leverage the power of the Chrome V8 Engine. Anyway events!....

Event Model Thingy?!?

So the model! The model is comprised of two parts an Event Emitter and an Event. An event as you already know could be any number of things from keyboard input, clicks etc and that is the same in Node. However an Event Emitter is the Node equivalent of an Event Handler in Javascript. On the server side it is an object which listens for events and keeps track of their respective callbacks when the event occurs. This object also provides a handy set of functions to define our events which we will see in a minute. The general lifecycle of the event model is as follows:

  1. We include the Events module in our project
  2. We create an Event Emitter to handle our events
  3. With our new instance we bind the event we want to listen for with the callback to execute.
  4. The event is emitted and picked up by our Event Emitter which initiates the callback once or repeatedly.
  5. Rinse and repeat

Sounds simple doesn't it? Lets see an example in action.

Example 1 - Simple Hello World Example

const EventEmitter = require('events'); // Require our events  
var e = new EventEmitter(); // Create a new instance

e.on('helloWorld', () => {  
    // Define our event we want to listen for
    console.log('Hello World');
});

e.emit('helloWorld'); // Emit our event to trigger the function  

As you can expect when we run this we get Hello World in the console. How about another example?

Example 2 - Passing data to our events

We can easily pass data into our events - Lets refactor our code to allow us to type in something and log it our. This example also gives us a good excuse to learn how to read user input:

const EventEmitter = require('events'); // Require our events  
const readline = require('readline');  
const reader = readline.createInterface({ // Create a new interface for reading  
  input: process.stdin, // Choose the input - in this case standard in for terminal
  output: process.stdout // Standard out for terminal
});
var e = new EventEmitter(); // Create a new instance


reader.question('What is your name?: ', (name) => { // Get our reader to ask a question  
    e.emit('logName', name); // Emit the logName event
    // OK this is contrived - we could log the name here directly however we want to learn events
});

e.on('logName', (name) => {  
    // Define our event we want to listen for
    console.log('Hello ' + name + ' the matrix has you!');
    reader.close(); // Close the read interface
});

So what we have done here is:

  1. Create a reading interface in node (so we can accept user input)
  2. Asked a question and accepted an input
  3. Emitted our event passing in the input as a parameter
  4. Logged the result

And as expected we get the following:

What is your name?: Dylan  
Hello Dylan the matrix has you!  

Example 3 - Firing a one time event

We can also fire one time events using the .once() method instead of .on(). This fires the event and removes the listener as such we only get the first .emit() value

const EventEmitter = require('events'); // Require our events

var e = new EventEmitter(); // Create a new instance

e.once('logName', (name) => {  
    // Note the use of .once() instead of .on()
    console.log('Hello ' + name + ' the matrix has you!');
});

e.emit('logName', 'Dylan');  
e.emit('logName', 'Fred'); // This won't fire  

Example 4 - Tick Tock

In this example we are going to create a ticking clock which tells us how many minutes have passed since we started. It will show us that we can emit events from within other events and how to setup intervals.

const EventEmitter = require('events');  
const ClockEvents = new EventEmitter(); // Define an emitter for our clock

var minuteCounter = 0; // Create a counter for the minutes passed

ClockEvents.on('tick', () => {  
    // Define the tick event - note .on() as this is a continual event
    // Time manipulation...
    var now = new Date();
    var seconds = now.getSeconds();
    var minutes = now.getMinutes();
    var hour = now.getHours();

    console.log(hour + ":"  + minutes + ":" + seconds); // Log current time
    if(seconds === 0) {
        // If seconds = 0 minute has passed
        ClockEvents.emit('minPassed'); // Emit the event
    }
});

ClockEvents.on('minPassed', () => {  
    // Define the min event
    minuteCounter = minuteCounter + 1; // Increment the counter
    console.log(minuteCounter + ' minute(s) passed.'); // Log the minutes
});

setInterval(() => {  
    // Set a timer to tick every second
    ClockEvents.emit('tick');
}, 1000);

Seems a lot going but in essence this is what happens:

  1. We create an event emitter
  2. Define our two events we want to handle tick and minPassed.
  3. The tick event gets the current time and logs it out every time. Then if the seconds are equal to zero a minute has passed and we emit out minPassed event which simple increments the counter and logs it out
  4. Lastly we define our interval much like we would on the client side which emits our tick event every second. Note that we don't require the timer events as this is baked into JS

Here is the truncated output for completeness

1:31:56  
1:31:57  
1:31:58  
1:31:59  
1:32:0  
8 minute(s) passed.  
1:32:1  
1:32:2  
1:32:3

...OMITTED...

1:32:58  
1:32:59  
1:33:0  
9 minute(s) passed.  
1:33:1  
1:33:2  
1:33:3  

Wrapping up...

Just to finish things up in this tutorial we have learned how to effectively handle events in NodeJS and leverage the power of the event model to do some cool things. These examples maybe fairly useless but hopefully you are now clearer on the principles and can apply them to more real world situations!