eventemitter4

0.2.4 • Public • Published

EventEmitter4

1. Synopsis
2. Installation
3. Browser support
4. Examples
4.1. Running the examples
4.2. Example 1
4.3. Example 2
4.4. Example 3
4.5. Example 4
4.6. Example 5
4.7. Example 6
4.8. Example 7
5. API
5.1. Emitter()
5.2. init()
5.3. on(event, listener)
5.4. once(event, listener)
5.5. onAny(listener)
5.6. emit(event, arg1,arg2,arg3,...)
5.7. removeListener(event, listener)
5.8. removeAllListeners([event])
5.9. toString()
6. Development tools
7. Building
8. Testing
9. Why EventEmitter4
9.1. Let us first debunk a common myth
9.2. underscore.js
9.3. Concerning performance
9.4. Why not the standard nodejs EventEmitter
9.5. Why not EventEmitter2
9.6. Why not EventEmitter3
10. Contact
10.1. Support
10.2. Projects
11. Other publications
12. License

1. Synopsis

EventEmitter4 is an alternative to node's built-in EventEmitter class, and to the existing alternatives EventEmitter2 and EventEmitter3 that should make your own code easier to debug.

2. Installation

You can install EventEmitter4 with npm:

npm install eventemitter4

3. Browser support

The module comes with a browserified version:

    browser-support/eventemitter4-bundle.js

And a minimized version thereof:

    browser-support/eventemitter4-bundle.min.js

4. Examples

4.1. Running the examples

Open a terminal. In order to run, for example, example 1:

cd myproject/node_modules/eventemitter4
node doc/examples/1-on.js

Classes that inherit from event emitters are notoriously difficult to debug. Do not hesitate to use the EventEmitter.toString() function to dump a copy of the internal state of the emitter. This will usually tell you what is going on, and where the bug is.

4.2. Example 1

For: EventEmitter4.on(event,listener)

Program

var EventEmitter=require('eventemitter4');
var emitter=new EventEmitter();
 
emitter.on('finished-eating',function(who) {
        console.log('clean up the table, '+who+'.');
});
 
emitter.emit('finished-eating','John');
 

Output

    clean up the table, John.

Any function registered on() an event will be triggered when the emitter invokes the emit() function with that particular event.

4.3. Example 2

Program

For: EventEmitter4.once(event,listener)

var EventEmitter=require('eventemitter4');
var emitter=new EventEmitter();
 
emitter.once('finished-eating',function(who) {
        console.log('clean up the table, '+who+'.');
});
 
emitter.emit('finished-eating','John');
emitter.emit('finished-eating','John');
emitter.emit('finished-eating','John');
 

Output

    clean up the table, John.

After triggering a listener registered with once(), it will fire one time for the event. After that it will not trigger any longer.

4.4. Example 3

For: EventEmitter4.onAny(listener)

Program

var EventEmitter=require('eventemitter4');
var emitter=new EventEmitter();
 
//Mum
emitter.on('finished-eating',function(who) {
        console.log('clean up the table, '+who+'.');
});
 
//Mum
emitter.on('finished-playing-ball',function(who) {
        console.log('go, take a shower, '+who+'.');
});
 
//john
emitter.onAny(function(who) {
        if(who==='John')
                console.log('yes, mum');
});
 
emitter.emit('finished-eating','John');
emitter.emit('finished-playing-ball','John');
emitter.emit('finished-playing-ball','Ann');
 

Output

    clean up the table, John.
    yes, mum
    go, take a shower, John.
    yes, mum
    go, take a shower, Ann.

On a particular event, Mum will issue an instruction for John, while John is registered to listen to any message. So, he hears everything. However, because of the if-clause, he only responds to events for which the argument who is John. Note that listeners are invoked in the order in which they were registered. Listeners registered with onAny() are triggered after all listeners registered with on().

4.5. Example 4

For: EventEmitter4.removeListener(event,listener)

Program

var EventEmitter=require('eventemitter4');
var emitter=new EventEmitter();
 
//Mum
emitter.on('finished-eating',function(who) {
        console.log('clean up the table, '+who+'.');
});
 
var john=function(who) {
        if(who==='John')
                console.log('yes, mum');
};
 
//John
emitter.on('finished-eating',john);
 
emitter.emit('finished-eating','John');
emitter.removeListener('finished-eating',john);
emitter.emit('finished-eating','John');
emitter.emit('finished-eating','John');
 

Output

    clean up the table, John.
    yes, mum
    clean up the table, John.
    clean up the table, John.

Both Mum and John are registered for the event. The first time John receives the event he responds. After that, the listener is removed, and John stops listening or responding. In this example, the result is in fact the same as if you had used once().

4.6. Example 5

For: EventEmitter4.removeAllListeners([event])

Program

var EventEmitter=require('eventemitter4');
var emitter=new EventEmitter();
 
//Mum
emitter.on('finished-eating',function() {
        console.log('clean up the table.');
});
 
//Mum
emitter.on('finished-playing-ball',function(who) {
        console.log('go, take a shower.');
});
 
var john=function() {
        console.log('yes, mum (john)');
};
 
//John
emitter.on('finished-eating',john);
emitter.on('finished-playing-ball',john);
 
var ann=function() {
        console.log('yes, mum (ann)');
};
 
//Ann
emitter.on('finished-eating',ann);
emitter.on('finished-playing-ball',ann);
 
emitter.emit('finished-playing-ball');
emitter.removeAllListeners('finished-playing-ball');
console.log('-- Mum, John and Ann now stop listening to the finished-playing-ball event.'+
        ' They still listen to the finished-eating event. --');
emitter.emit('finished-playing-ball'); //in vain
emitter.emit('finished-playing-ball'); //in vain
emitter.emit('finished-eating');
 

Output

    go, take a shower.
    yes, mum (john)
    yes, mum (ann)
    -- Mum, John and Ann now stop listening to the finished-playing-ball event. They still listen to the finished-eating event. --
    clean up the table.
    yes, mum (john)
    yes, mum (ann)

In this example, the Mum, John and Ann listeners are registered for both events. Then, we remove all listeners for one event. None of the listeners responds any longer to that event.

4.7. Example 6

Program

For: just one possible way to inherit from EventEmitter4

var EventEmitter=require('eventemitter4');
var _=require('underscore');
 
function Mum() { 
 
        if (!(this instanceof Mum))
                throw "this function must be called with the new operator";
        
        this.init();
}
 
//extend Mum with EventEmitter
_.extend(Mum.prototype,EventEmitter.prototype);
 
//Calls the parent init() function
Mum.prototype.init=function() {
        EventEmitter.prototype.init.call(this);
        this.whatever='I am mum';        
}
 
//This function configures Mum to give orders
Mum.prototype.givesOrdersToKids=function() {
 
        console.log('giving order to John');
        this.emit('give-order-to-kid','John');
 
        console.log('giving order to Ann');
        this.emit('give-order-to-kid','Ann');
}
 
var mum=new Mum();
 
//John
mum.on('give-order-to-kid',function(who) {
        if(who==='John')
                console.log('Yes, mum (John)');
});
 
//Ann
mum.on('give-order-to-kid',function(who) {
        if(who==='Ann')
                console.log('Yes, mum (Ann)');
});
 
//
mum.givesOrdersToKids();
 

Output

    giving order to John
    Yes, mum (John)
    giving order to Ann
    Yes, mum (Ann)

There are many ways to organize inheritance in Javascript. In this particular one, we use underscore.js to extend the prototype of the inheriting class with the functions of the parent class.

4.8. Example 7

Program

For: EventEmitter4.toString()

var EventEmitter=require('eventemitter4');
var emitter=new EventEmitter();
 
emitter.on('finished-eating',function() {});
emitter.on('finished-eating',function() {});
emitter.on('finished-eating',function() {});
emitter.on('finished-playing-ball',function() {});
 
console.log(emitter.toString());
 

Output

    {"listeners":{"finished-eating":3,"finished-playing-ball":1},
    "onceListeners":{},
    "listenersToAnyEvent":0}

After registering 4 listeners on 2 different events, you can see in the internal state of the emitter for which events there are listeners registered. This should help with debugging situations in which you expected something else.

5. API

5.1. Emitter()

Constructor. Creates an emitter object.

5.2. init()

Initializes the emitter's data structure. Is called by the constructor and must be called by any class that inherits from this one.

5.3. on(event, listener)

Registers a listener function for a particular event.

Params

  • string event The event for which to register the listener function.
  • function listener The listener function to register.

5.4. once(event, listener)

Registers a listener function for a particular event. The listener will be triggered at most once.

Params

  • string event The event for which to register the listener function.
  • function listener The listener function to register.

5.5. onAny(listener)

Registers a listener function for all events. The listener can retrieve what event it was called for from the emitter.event variable

Params

  • function listener The listener function to register.

5.6. emit(event, arg1,arg2,arg3,...)

Emits an event and triggers the listeners registered for this event. function(event, [arg1, arg2, arg3, ...])

Params

  • function event The event to trigger.
  • any arg1,arg2,arg3,... Optional parameters that will be transmitted to the listener. As many parameters as needed can be transmitted.

5.7. removeListener(event, listener)

Removes a listener for a particular event.

Params

  • string event The event for which to remove the listener function.
  • function listener The listener function to remove.

5.8. removeAllListeners([event])

Removes a listener for a particular event or from all events

Params

  • string [event] The event for which to remove the listener function. If left undefined, the listener will be removed for all events.

5.9. toString()

Dumps the current state of the emitter into string.

6. Development tools

7. Building

Execute the build.sh script to re-build the project from sources. It will re-generate the browser support files and the documentation.

8. Testing

Open a terminal and use mocha to run the unit tests:

cd node_modules/eventemitter4
mocha

9. Why EventEmitter4

The main reason why I wrote EventEmitter4 is that I had run into an untractable bug in my own program and that the only way to solve the problem was by dumping the internal state of the emitter class that I was using, that is, EventEmitter2. Dumping the internal state of EventEmitter2 was surprisingly hard, simply because the over-optimized code was difficult to follow, execution after execution. That is the main reason why my own bug became hard to solve.

9.1. Let us first debunk a common myth

The idea behind the information hiding concept is that users of a module should only know about the functions listed in the module's API. This is true, until there are bugs either in your own program or even in the external module itself.

The bugs in your own program may very well cause the module's private variables to be in a state that you did not expect. You may not be able to detect this just be looking at the internal state of your own program. You will need to have a close look at the internal state of the module. If there is no way in which the API of the module can expose its full state to you, you may be unable to solve the bug in your own program.

It is well known that debugging EventEmitter issues in your own program can be seriously tricky. That is why the EventEmitter4 class implements a toString() function that dumps a summary of its internal state.

If you use the EventEmitter4 class, you should know that it stores its ordinary listeners in an object in which each key is an event, and in which each entry contains an array of listener functions. The once-only listeners are stored in exactly the same way. The listeners to all events are just stored in an array. The numbers that you can see in the toString() internal state dump of EventEmitter4, are just a count of the listener functions that it contains in that particular array.

When you only just use the module, you may still be able to get away without understanding how its internal state is stored and what the values are at any point in time, but when you need to extend the module, this is usually no longer true. Your own functions will probably have to manipulate that internal state too. Most users of any alternative EventEmitter class will actually be extending it by inheriting it in their own classes.

Therefore, the idea that they do not need to know how the internal storage of the EventEmitter class works, is not particularly true. Furthermore, from the issue lists in the alternative EventEmitter projects, you can easily see that many of these users want to add more functions to the EventEmitter class. Most of these feature requests are only applicable to very specific situations. There would be no point in burdening the core project with such sometimes very ideosyncratic requirements. It would be better if these users could actually do that by themselves.

9.2. underscore.js

When using a library like underscore.js, it is almost trivial to manage the internal data structure of a class like EventEmitter4.

Some people call a tool like underscore.js a tool of functional programming. It is true that probably all of its functions are idempotent and therefore, do not keep internal state. That part is indeed the same what functional programming languages try to achieve, but functional programming tends to be much more than that.

In my opinion, underscore.js is better termed an implementation of set algebra. It pretty much does the same as SQL, but then just on one table in memory. The main difference between underscore.js and SQL, is that underscore.js does not implement functions for dealing with the cartesian product, that is, joining multiple sets.

The use of set algebra often turns functions that deal with tree data structures into one-liners. Since EventEmitter4 only deals with trees of maximum two levels -- which is a relatively simple data structure -- the use of set algebra results in surprisingly short functions. These functions are at the same time much easier to read.

It also means that you can easily put console.log() statements in the module's source code, if you ever need it. Furthermore, this also means that it is much easier for other people to help fixing bugs or offer pull requests.

9.3. Concerning performance

Since all real work in EventEmitter4 is done by calling into underscore.js, it relegates any performance issue to that module. EventEmitter4 has no performance characteristics on its own.

I strongly suspect that the performance for underscore.js -- up till now I have not had time or a pressing need to read its source code -- is to an important extent relegated to the performance of the implementation of Object and Array classes in nodejs. Since underscore.js is based on the use of repeated function calls, it also counts on the Javascript engine to minimize its overhead when executing function calls.

Typical NodeJS code is replete with listeners functions and callback functions. There would be no point in trying to fix any performance issue related to that, just in EventEmitter4.

The responsibility for those areas falls squarely onto the Google v8 team. If they can make further performance improvements in those areas, they would not just improve the performance of EventEmitter4 but of all possible modules and applications.

In other words, one reason why an alternative EventEmitter implementation could be fundamentally faster than EventEmitter4, is that it uses faster data structures than the standard Object and Array classes. The EventEmitter2 and EventEmitter3 libraries definitely do not do this.

Painstakingly spelling out data manipulations manually, instead of using set algebra, will not necessarily make their programs any faster, but it will bloat the source code and make it more difficult to read.

I did not go down the route of replacing set algebra functions by manually-devised code, because the performance in ultra-simple code such as EventEmitter4, is something that can only be improved at lower levels.

9.4. Why not the standard nodejs EventEmitter

I really needed an onAny() function in my project. The standard nodejs EventEmitter class does not implement it. At the same time, the internal data structure of the standard EventEmitter class is not particularly documented anywhere, as far as I know. You would have to painstakingly excise it from its source code, just like I was eventually forced to do for EventEmitter2.

9.5. Why not EventEmitter2

EventEmitter2 does have an onAny() function.

However, there is no way to dump the state of its emitter objects. Because it also supports wildcards, such as on('foo.*'), the EventEmitter2 source code became large and is rather difficult to figure out.

I managed to place console.log() statements to check the state of the EventEmitter2 class, in order to detect a bug in my own program, but it was unnecessarily hard. The module is also replete with performance optimizations of which I do not believe for a second that they do anything for performance at all. These redundant optimizations still manage to further complicate the already overburdened source code of the module.

In my opinion, there is actually no reasonable justification for implementing the on('foo.*') functionality. You can just use onAny() and trivially skip the events that you do not need:

onAny(function() {
 
        if(!/foo\.*/.test(emitter.event)) return;
 
        //remainder of your listener code
 
});

Is it really necessary to complicate the source code of the module tremendously in order to gain so little?

9.6. Why not EventEmitter3

EventEmitter3 does not have an onAny() function. The module is not implemented using any form of set algebra either. Therefore, I did not particularly feel like extending it.

Consequently, instead of painstakingly extending any of these alternative EventEmitter classes, I made the decision to roll my own.

10. Contact

10.1. Support

For trouble tickets with EventEmitter4, please, use the github issue list.

10.2. Projects

I am available for commercial projects.

In commercial projects, I often do the initial prototyping by myself. After that, I manage external developer contributions through github and bitbucket. I usually end up being the long-term go-to person for how to evolve the system. My work involves reviewing Javascript for both the web and nodejs. I occasionally still do PHP. The startups I work for, are usually located elsewhere, but I do all of my work from Cambodia. If you are in need of a source code manager or quality assurance person for your project, feel free to contact me at erik@sankuru.biz.

11. Other publications

12. License

    RPC Websocket
    Written by Erik Poupaert, Cambodia
    (c) 2014
    Licensed under the LGPL

Package Sidebar

Install

npm i eventemitter4

Weekly Downloads

6

Version

0.2.4

License

LGPL

Last publish

Collaborators

  • eriksank