How to use Event Emitters with ES5 and ES6 in Node.js easily

How to use Event Emitters with ES5 and ES6 in Node.js easily

Nowadays there are a lot of open source libraries that you can use within your projects. They're pretty nice worked and allow you to do a lot of things to know when things happen via events. They allow you normally add a callback to certainly events triggered internally. Have you ever wondered, what's the right and easiest way to built such a library that allows to use callbacks ? Well, whether if this is the right way to do it is not the absolute truth, but a lot of libraries rely on the Event Emitter class of Node.js.

All objects that emit events are instances of the EventEmitter class, these objects expose an eventEmitter.on() function that allows one or more functions to be attached to named events emitted by the object. Typically, event names are camel-cased strings but any valid JavaScript property key can be used.

In this article you will learn how to use them for both ECMAScript 5 and 6.

ES5

If you're using ECMAScript 5, the usage of event emitters couldn't be so clear for beginners in Javascript:

// yourLibrary.js

// Instantiate event emitter and inherits
var EventEmitter = require('events');
var inherits = require('util').inherits;

// Create the constructor of YourLibrary and add the EventEmitter to the this context
function YourLibrary() {
    EventEmitter.call(this);
}

// Use Inheritance to add the properties of the DownloadManager to event emitter
inherits(YourLibrary, EventEmitter);

// Export YourLibrary !
module.exports = YourLibrary;

When EventEmitter.call(this) is executed during the creation of an instance from YourLibrary, it appends properties declared from the EventEmitter constructor to YourLibrary. Then the inherits function inherits the prototype methods from one constructor into another (your constructor YourLibrary and the super constructor EventEmitter), in this way the prototype of your constructor will be set to a new object created from superConstructor.

As your library obviously won't offer the same methods of EventEmitter you need to add your own functions to YourLibrary by using prototyping before or after the module.exports line:

//yourLibrary.js

YourLibrary.prototype.testAsyncMethod = function testAsyncMethod(someData) {
    _this = this;

    // Execute the data event in 2 seconds
    setTimeout(function(){
        // Emit the data event that sends the same data providen by testAsyncMethod 
        _this.emit("data", someData);
    }, 2000);
};

The previous function adds the testAsyncMethod to your library, this method expects some data as first argument and it will sent again through the data event, emitted with the inherited method emit from the EventEmitter class. In this way YourLibrary uses the event manager of Node.js and it can be used to create organized and easy to read code:

// otherfile.js

// Require YourLibrary file
var YourLibrary = require("./yourLibrary");

// Create an instance of YourLibrary
var libInstance = new YourLibrary();

// Add the "data" event listener to your library and add some callback
libInstance.on("data", function(data){
    // Outputs: "Hello World, data test"
    console.log(data);
});

// Execute the testAsyncMethod of your library
libInstance.testAsyncMethod("Hello World, data test");

Although to handle asynchronous events can be a little bit tricky, everything will make sense at the end, so if you don't understand it yet, be patient and analyse the example carefully.

ES6

With EcmaScript 6, this task has been really simplified and its pretty easier to handle and understand than with ES5. However it works in the same way: by using the extends keyword, your class can extend the EventEmitter inheriting obviously its method and therefore be able to use the emit method to trigger an event:

const EventEmitter = require('events');

class YourLibrary extends EventEmitter {
    constructor() {
        super();
    }

    testAsyncMethod(data) {
        this.emit('data', data);
    }
}

module.exports = YourLibrary

Then YourLibrary can be easily used from another file:

const MyLibrary = require('./yourLibrary');

const libInstance = new MyLibrary();

libInstance.on('data', (data) => {
    // Outputs : Received data: "Hello World, data test"
    console.log(`Received data: "${data}"`);
});

libInstance.testAsyncMethod("Hello World, data test");

As you can see, it's pretty easier than handling the same code with ES6.

Example using ECMAScript 5

If you didn't get it with the introduction, don't worry, we're sure that handling the example as a library in the real life (or something similar) will help you to understand. In this example, imagine that we are going to use a Download Manager library, this library is really simple, it offers a way to download a file from your server asynchronously. Using the Event Emitters class of Node.js, you are able to know when the download of the file finishes and allows you to know the progress of the download.

The example doesn't download anything really, we are just simulating a download using the setInterval (for progress event) and the setTimeout (for downloadSuccess event) function. Create the fileDownloader.js in some folder of your workspace and add the following code on it:

// fileDownloader.js

var EventEmitter = require('events');
var inherits = require('util').inherits;

// Create the constructor of DownloadManager and add the EventEmitter to the this context
function DownloadManager() {
    EventEmitter.call(this);
}

// Use Inheritance to add the properties of the event emitter to DownloadManager
inherits(DownloadManager, EventEmitter);

// Export the Download Manager
module.exports = DownloadManager;


//
// Write your library down here by prototyping !
//

/**
 * @param URL {String} Url of the imaginary file to download
 */
DownloadManager.prototype.downloadAsync = function downloadAsync(URL) {
    var _this = this;
    var progress = 0;

    console.log('Download file "' + URL + '" ...');

    // Trigger the progress event every second
    var progressInterval = setInterval(function() {
        progress += 20;
        
        // Emit progress event with the progress as argument
        _this.emit('progress' , progress);
    }, 1000);

    // Trigger the downloadSuccess event in 5.5 seconds and clear the progress interval
    setTimeout(function() {
        var optionalDataResponse = {
            filename: "imaginary.txt",
            filesize: 123123,
            fileUrl: URL
        };

        // Stop triggering progress
        clearInterval(progressInterval);
        
        // Use the emit method of the EventEmitter to trigger the downloadSuccess event !
        _this.emit('downloadSuccess' , optionalDataResponse);
    }, 5500);
};

Then the Download Manager can be used as follows (in this example on the index.js file):

// index.js

// Require the download manager library
var DownloadManager = require("./fileDownloader");

// Create an instance of the Download Manager
var downloader = new DownloadManager();

// Add event listener of the progress of download
downloader.on("progress", function (progress){
    console.log('Download progress: '+ progress +'%');
});

// Do something when the download of the file ends
downloader.on("downloadSuccess", function (response){
    //{
    //    filename: "imaginary.txt",
    //    filesize: 123123
    //}
    console.log(response);
});

// Start download
downloader.downloadAsync("file.txt");

And executing the script using node index.js you'll get the following output:

Download file "file.txt" ...
Download progress: 20%
Download progress: 40%
Download progress: 60%
Download progress: 80%
Download progress: 100%

{ filename: 'imaginary.txt',
  filesize: 123123,
  fileUrl: 'file.txt' }

Example using ECMAScript 6

For the example in ES6, we'll use the same idea of the first example in ES5, imagine that we are going to use a Download Manager library, this library is really simple, it offers a way to download a file from your server asynchronously. Using the Event Emitters class of Node.js, you are able to know when the download of the file finishes and allows you to know the progress of the download.

The example doesn't download anything really, we are just simulating a download using the setInterval (for progress event) and the setTimeout (for downloadSuccess event) function. Create the fileDownloader.js in some folder of your workspace and add the following code on it:

const EventEmitter = require('events');

class DownloadManager extends EventEmitter {
    constructor() {
        super();
    }

    downloadAsync(URL) {
        let _this = this;
        let progress = 0;

        console.log(`Download file '${URL}' ...`);

        // Trigger the progress event every second
        let progressInterval = setInterval(() => {
            progress += 20;

            // Emit progress event with the progress as argument
            _this.emit('progress', progress);
        }, 1000);

        // Trigger the downloadSuccess event in 5.5 seconds and clear the progress interval
        setTimeout(() => {
            let optionalDataResponse = {
                filename: "imaginary.txt",
                filesize: 123123,
                fileUrl: URL
            };

            // Stop triggering progress
            clearInterval(progressInterval);

            // Use the emit method of the EventEmitter to trigger the downloadSuccess event !
            _this.emit('downloadSuccess', optionalDataResponse);
        }, 5500);
    }
}

module.exports = DownloadManager

The download manager can be used in other files (index.js):

// index.js

// Require the download manager library
const DownloadManager = require('./fileDownloader');

// Create an instance of the Download Manager
const downloader = new DownloadManager();

// Add event listener of the progress of download
downloader.on("progress", (progress) => {
    console.log(`Download progress ${progress}%`);
});

// Do something when the download of the file ends
downloader.on("downloadSuccess", (response) => {
    //{
    //    filename: "imaginary.txt",
    //    filesize: 123123
    //}
    console.log(response);
});

// Start download
downloader.downloadAsync("file.txt");

Executing the script using node index.js you'll get the following output (same as the example in ES5):

Download file "file.txt" ...
Download progress: 20%
Download progress: 40%
Download progress: 60%
Download progress: 80%
Download progress: 100%

{ filename: 'imaginary.txt',
  filesize: 123123,
  fileUrl: 'file.txt' }

As you are using the EventEmitter class of Node.js you are able to use all the methods of this class in your library, so don't forget to check the docs of Node.js to know more about the Event Emitters.

Happy coding !

Become a more social person