EventEmitter as Delegate using Traits

traits node javascript
October 11, 2013

One of the most used modules in NodeJS is events. This module exposes a class called EventEmitter that allows you to listen to events in your application using the on or addListener methods. You can, as well, emit or fire your own custom events using emit.

Commonly, you would create an instance of EventEmitter or you could extend from it when you want to give your class the ability to listen to or fire events.

index.js

var EventEmitter = require('events').EventEmitter,
    emitter;

function onExecuted(){ /* handle event here */ };

emitter = new EventEmitter();

emitter.on('executed', onExecuted);

emitter.emit('executed');

When the executed event is emitted, then the onExecuted function will be called.

Extending from EventEmitter

There are tons of posts about how to use EventEmitter, but basically the idea or the most common scenario in NodeJS world, is to create your class extending from EventEmitter.

Task.js

var EventEmitter = require('events').EventEmitter,
    Task;

//....
Task = function() {
    EventEmitter.call(this);
    //....
};

Task.prototype.__proto__ = EventEmitter.prototype

//....

Task.prototype.execute: function() {
    //....
    this.emit('executed');
}

module.exports = Task;

So now we can have Tasks firing our custom event.

index.js

var Task = require('./Task'),
    task;

task = new Task();

task.on('executed', function(){/* handle event here */})

task.execute();

Here, our execute method in Task fires the executed event.

Compositon over inheritance

This design principle is also applicable to NodeJS modules. We want to have a inheritance tree that reflects the problem we are trying to solve, not how we solve it. Saying that a A Task is an EventEmitter might not have a meaning in our solution.

Read more about this here

EventEmitter using Composition

If we add an EventEmitter instance property to our Task, then we can delegate the methods for adding listeners or firing event to that property.

Task.js

var EventEmitter = require('events').EventEmitter,
    Task;

//....
Task = function() {
    this._emitter = new EventEmitter();
    //....
};

// Delegation methods:

Task.prototype.on = function() {
    this._emitter.on.apply(this._emitter, arguments);  
};

Task.prototype.emit = function() {
    this._emitter.emit.apply(this._emitter, arguments);  
};

//....

Task.prototype.execute: function() {
    //....
    this.emit('executed');
}

module.exports = Task;

With this change we can still execute our index.js file without changing anything else since we just created the EventEmitter methods as delegated ones in our Task.js class definition.

Reusing code

This code is simple but, if we want to add a new class to represent a Step and we need the Step to be able to listen to events then we will ending up copying and pasting the code for the delegation methods into our Step class definition.

Using Traits to share delegation code

All of the above applies to any class created with CocktailJS. But, we have another mechanism that helps when we want to reuse code. Traits.

Extracting all that code that we want to share into a Trait will be very easy: We just want to have -in this case- on and emit methods, so our Trait will be defining only those two:

Eventable.js

var cocktail = require('cocktail');

cocktail.mix({
    '@exports' : module, 
    '@requires': ['getEmitter'],

    on: function() {
        this.getEmitter().on.apply(this.getEmitter(), arguments);
    },

    emit: function() {
        this.getEmitter().emit.apply(this.getEmitter(), arguments);
    }

}) ;

The Eventable trait requires a getEmitter method defined in the class where we are going to apply it. And here, getEmitter is expected to return an instance of EventEmitter or any other object that has a similar public api. So you can implement your own event emitter if you would like to do it.

Now, we have to apply the Trait Eventable to our Task class:

Task.js

var cocktail     = require('cocktail'),
    EventEmitter = require('events').EventEmitter,
    Eventable    = require('./Eventable');

cocktail.mix({
    '@exports': module,
    '@as'     : 'class',

    '@traits' : [Eventable],

    '@properties' : {
        emitter : undefined
    },

    constructor: function() {
        this.setEmitter(new EventEmitter());
    },

    execute: function() {
        this.emit('executed');
    }
});

With these changes we can still execute our index.js with no problems. And in case we need a new class Step to fire and listen to events, we can reuse the Eventable trait in the same way as we did for Task:

Step.js

var cocktail     = require('cocktail'),
    EventEmitter = require('events').EventEmitter,
    Eventable    = require('./Eventable');

cocktail.mix({
    '@exports': module,
    '@as'     : 'class',

    '@traits' : [Eventable],

    '@properties' : {
        emitter     : undefined,
        description : 'default step description'
    },

    constructor: function() {
        this.setEmitter(new EventEmitter());
    },

    done : function() {
        this.emit('done');
    },

    check : function() {
        // do some check here ...
    }

});

Even more reusable code: @evented Annotation

CocktailJS relies on annotations to perform tasks over classes. It provides as well a mechanism that allows you to create your own annotations too. In this case, we have a few steps we are doing to apply the Eventable trait, and those steps are the same in our Task and Step classes. We can, then, create a process to do that for us implementing a custom annotation.

Evented.js

var cocktail  = require('cocktail'),
    Eventable = require('./Eventable'),
    Emitter   = require('events').EventEmitter;

cocktail.mix({
    '@annotation': 'evented',
    '@exports'   : module,
    '@as'        : 'class',

    '@properties': {
        parameter: undefined
    },

    process: function(subject){
        var emitter = this.getParameter();

        if(emitter) {

            cocktail.mix(subject, {
                '@traits': [Eventable],

                _emitter: undefined,

                getEmitter: function(){
                    if(!this._emitter){
                        this._emitter = (emitter === true) ? new Emitter() : emitter;
                    }

                    return this._emitter;
                }
            });

        }
    }

});

The Evented Annotation will apply the Eventable trait for us. If the annotation parameter is true then we are going to create the EventEmitter instance but, you can pass your own emitter instance too.

Refactoring our Task and Step classes to use the @evented annotation:

Task.js

var cocktail = require('cocktail'),
    Evented  = require('./Evented');

cocktail.use(Evented);

cocktail.mix({
    '@exports': module,
    '@as'     : 'class',

    '@evented': true,

    execute: function() {
        this.emit('executed');
    }
});

Step.js

var cocktail = require('cocktail'),
    Evented  = require('./Evented');

cocktail.use(Evented);

cocktail.mix({
    '@exports': module,
    '@as'     : 'class',

    '@evented': true,

    '@properties' : {
        description : 'default step description'
    },

    done : function() {
        this.emit('done');
    },

    check : function() {
        // do some check here ...
    }

});

Final words

We have seen here a few design principles and some features behind CocktailJS that help to reduce and reuse code. Now it is time for you to experiment with it and let us know what's your experience, thoughts, etc.

comments powered by Disqus