Giter VIP home page Giter VIP logo

eventemitter2's Introduction

Build Status Coverage Status NPM version Dependency Status npm

SYNOPSIS

EventEmitter2 is an implementation of the EventEmitter module found in Node.js. In addition to having a better benchmark performance than EventEmitter and being browser-compatible, it also extends the interface of EventEmitter with many additional non-breaking features.

If you like this project please show your support with a GitHub ⭐!

DESCRIPTION

FEATURES

  • ES5 compatible UMD module, that supports node.js, browser and workers of any kind
  • Namespaces/Wildcards
  • Any listeners
  • Times To Listen (TTL), extends the once concept with many
  • Async listeners (using setImmediate|setTimeout|nextTick) with promise|async function support
  • The emitAsync method to return the results of the listeners via Promise.all
  • Subscription methods (on, once, many, ...) can return a listener object that makes it easy to remove the subscription when needed - just call the listener.off() method.
  • Feature-rich waitFor method to wait for events using promises
  • listenTo & stopListeningTo methods for listening to an external event emitter of any kind and propagate its events through itself using optional reducers/filters
  • Extended version of the events.once method from the node events API
  • Browser & Workers environment compatibility
  • Demonstrates good performance in benchmarks
Platform: win32, x64, 15267MB
Node version: v13.11.0
CPU: 4 x AMD Ryzen 3 2200U with Radeon Vega Mobile Gfx @ 2495MHz
----------------------------------------------------------------
EventEmitterHeatUp x 2,897,056 ops/sec ±3.86% (67 runs sampled)
EventEmitter x 3,232,934 ops/sec ±3.50% (65 runs sampled)
EventEmitter2 x 12,261,042 ops/sec ±4.72% (59 runs sampled)
EventEmitter2 (wild) x 242,751 ops/sec ±5.15% (68 runs sampled)
EventEmitter2 (wild) using plain events x 358,916 ops/sec ±2.58% (78 runs sampled)
EventEmitter2 (wild) emitting ns x 1,837,323 ops/sec ±3.50% (72 runs sampled)
EventEmitter2 (wild) emitting a plain event x 2,743,707 ops/sec ±4.08% (65 runs sampled)
EventEmitter3 x 10,380,258 ops/sec ±3.93% (67 runs sampled)

Fastest is EventEmitter2

What's new

To find out what's new see the project CHANGELOG

Differences (Non-breaking, compatible with existing EventEmitter)

  • The EventEmitter2 constructor takes an optional configuration object with the following default values:
var EventEmitter2 = require('eventemitter2');
var emitter = new EventEmitter2({

  // set this to `true` to use wildcards
  wildcard: false,

  // the delimiter used to segment namespaces
  delimiter: '.', 

  // set this to `true` if you want to emit the newListener event
  newListener: false, 

  // set this to `true` if you want to emit the removeListener event
  removeListener: false, 

  // the maximum amount of listeners that can be assigned to an event
  maxListeners: 10,

  // show event name in memory leak message when more than maximum amount of listeners is assigned
  verboseMemoryLeak: false,

  // disable throwing uncaughtException if an error event is emitted and it has no listeners
  ignoreErrors: false
});
  • Getting the actual event that fired.
emitter.on('foo.*', function(value1, value2) {
  console.log(this.event, value1, value2);
});

emitter.emit('foo.bar', 1, 2); // 'foo.bar' 1 2
emitter.emit(['foo', 'bar'], 3, 4); // 'foo.bar' 3 4

emitter.emit(Symbol(), 5, 6); // Symbol() 5 6
emitter.emit(['foo', Symbol()], 7, 8); // ['foo', Symbol()] 7 8

Note: Generally this.event is normalized to a string ('event', 'event.test'), except the cases when event is a symbol or namespace contains a symbol. In these cases this.event remains as is (symbol and array).

  • Fire an event N times and then remove it, an extension of the once concept.
emitter.many('foo', 4, function() {
  console.log('hello');
});
  • Pass in a namespaced event as an array rather than a delimited string.
emitter.many(['foo', 'bar', 'bazz'], 4, function() {
  console.log('hello');
});

Installing

$ npm install eventemitter2

Or you can use unpkg.com CDN to import this module as a script directly from the browser

API

Types definition

  • Event: string | symbol
  • EventNS: string | Event []

Class EventEmitter2

instance:

static:

The event argument specified in the API declaration can be a string or symbol for a simple event emitter and a string|symbol|Array(string|symbol) in a case of a wildcard emitter;

When an EventEmitter instance experiences an error, the typical action is to emit an error event. Error events are treated as a special case. If there is no listener for it, then the default action is to print a stack trace and exit the program.

All EventEmitters emit the event newListener when new listeners are added. EventEmitters also emit the event removeListener when listeners are removed, and removeListenerAny when listeners added through onAny are removed.

Namespaces with Wildcards To use namespaces/wildcards, pass the wildcard option into the EventEmitter constructor. When namespaces/wildcards are enabled, events can either be strings (foo.bar) separated by a delimiter or arrays (['foo', 'bar']). The delimiter is also configurable as a constructor option.

An event name passed to any event emitter method can contain a wild card (the * character). If the event name is a string, a wildcard may appear as foo.*. If the event name is an array, the wildcard may appear as ['foo', '*'].

If either of the above described events were passed to the on method, subsequent emits such as the following would be observed...

emitter.emit(Symbol());
emitter.emit('foo');
emitter.emit('foo.bazz');
emitter.emit(['foo', 'bar']);
emitter.emit(['foo', Symbol()]);

NOTE: An event name may use more than one wildcard. For example, foo.*.bar.* is a valid event name, and would match events such as foo.x.bar.y, or ['foo', 'bazz', 'bar', 'test']

Multi-level Wildcards

A double wildcard (the string **) matches any number of levels (zero or more) of events. So if for example 'foo.**' is passed to the on method, the following events would be observed:

emitter.emit('foo');
emitter.emit('foo.bar');
emitter.emit('foo.bar.baz');
emitter.emit(['foo', Symbol(), 'baz']);

On the other hand, if the single-wildcard event name was passed to the on method, the callback would only observe the second of these events.

emitter.addListener(event, listener, options?: object|boolean)

emitter.on(event, listener, options?: object|boolean)

Adds a listener to the end of the listeners array for the specified event.

emitter.on('data', function(value1, value2, value3, ...) {
  console.log('The event was raised!');
});
emitter.on('data', function(value) {
  console.log('The event was raised!');
});

Options:

  • async:boolean= false- invoke the listener in async mode using setImmediate (fallback to setTimeout if not available) or process.nextTick depending on the nextTick option.

  • nextTick:boolean= false- use process.nextTick instead of setImmediate to invoke the listener asynchronously.

  • promisify:boolean= false- additionally wraps the listener to a Promise for later invocation using emitAsync method. This option will be activated by default if its value is undefined and the listener function is an asynchronous function (whose constructor name is AsyncFunction).

  • objectify:boolean= false- activates returning a listener object instead of 'this' by the subscription method.

listener

The listener object has the following properties:

  • emitter: EventEmitter2 - reference to the event emitter instance
  • event: event|eventNS - subscription event
  • listener: Function - reference to the listener
  • off(): Function- removes the listener (voids the subscription)
var listener= emitter.on('event', function(){
  console.log('hello!');
}, {objectify: true});

emitter.emit('event');

listener.off();

Note: If the options argument is true it will be considered as {promisify: true}

Note: If the options argument is false it will be considered as {async: true}

var EventEmitter2= require('eventemitter2');
var emitter= new EventEmitter2();

emitter.on('event', function(){
    console.log('The event was raised!');
}, {async: true});

emitter.emit('event');
console.log('emitted');

Since the async option was set the output from the code above is as follows:

emitted
The event was raised!

If the listener is an async function or function which returns a promise, use the promisify option as follows:

var EventEmitter2= require('eventemitter2');
var emitter= new EventEmitter2();

emitter.on('event', function(){
    console.log('The event was raised!');
    return new Promise(function(resolve){
       console.log('listener resolved');
       setTimeout(resolve, 1000);
    });
}, {promisify: true});

emitter.emitAsync('event').then(function(){
    console.log('all listeners were resolved!');
});

console.log('emitted');

Output:

emitted
The event was raised!
listener resolved
all listeners were resolved!

If the promisify option is false (default value) the output of the same code is as follows:

The event was raised!
listener resolved
emitted
all listeners were resolved!

emitter.prependListener(event, listener, options?)

Adds a listener to the beginning of the listeners array for the specified event.

emitter.prependListener('data', function(value1, value2, value3, ...) {
  console.log('The event was raised!');
});

options:

options?: See the addListener options

emitter.onAny(listener)

Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the callback.

emitter.onAny(function(event, value) {
  console.log('All events trigger this.');
});

emitter.prependAny(listener)

Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the callback. The listener is added to the beginning of the listeners array

emitter.prependAny(function(event, value) {
  console.log('All events trigger this.');
});

emitter.offAny(listener)

Removes the listener that will be fired when any event is emitted.

emitter.offAny(function(value) {
  console.log('The event was raised!');
});

emitter.once(event | eventNS, listener, options?)

Adds a one time listener for the event. The listener is invoked only the first time the event is fired, after which it is removed.

emitter.once('get', function (value) {
  console.log('Ah, we have our first value!');
});

options:

options?: See the addListener options

emitter.prependOnceListener(event | eventNS, listener, options?)

Adds a one time listener for the event. The listener is invoked only the first time the event is fired, after which it is removed. The listener is added to the beginning of the listeners array

emitter.prependOnceListener('get', function (value) {
  console.log('Ah, we have our first value!');
});

options:

options?: See the addListener options

emitter.many(event | eventNS, timesToListen, listener, options?)

Adds a listener that will execute n times for the event before being removed. The listener is invoked only the first n times the event is fired, after which it is removed.

emitter.many('get', 4, function (value) {
  console.log('This event will be listened to exactly four times.');
});

options:

options?: See the addListener options

emitter.prependMany(event | eventNS, timesToListen, listener, options?)

Adds a listener that will execute n times for the event before being removed. The listener is invoked only the first n times the event is fired, after which it is removed. The listener is added to the beginning of the listeners array.

emitter.many('get', 4, function (value) {
  console.log('This event will be listened to exactly four times.');
});

options:

options?: See the addListener options

emitter.removeListener(event | eventNS, listener)

emitter.off(event | eventNS, listener)

Remove a listener from the listener array for the specified event. Caution: Calling this method changes the array indices in the listener array behind the listener.

var callback = function(value) {
  console.log('someone connected!');
};
emitter.on('get', callback);
// ...
emitter.removeListener('get', callback);

emitter.removeAllListeners([event | eventNS])

Removes all listeners, or those of the specified event.

emitter.setMaxListeners(n)

By default EventEmitters will print a warning if more than 10 listeners are added to it. This is a useful default which helps finding memory leaks. Obviously not all Emitters should be limited to 10. This function allows that to be increased. Set to zero for unlimited.

emitter.getMaxListeners()

Returns the current max listener value for the EventEmitter which is either set by emitter.setMaxListeners(n) or defaults to EventEmitter2.defaultMaxListeners

emitter.listeners(event | eventNS)

Returns an array of listeners for the specified event. This array can be manipulated, e.g. to remove listeners.

emitter.on('get', function(value) {
  console.log('someone connected!');
});
console.log(emitter.listeners('get')); // [ [Function] ]

emitter.listenersAny()

Returns an array of listeners that are listening for any event that is specified. This array can be manipulated, e.g. to remove listeners.

emitter.onAny(function(value) {
  console.log('someone connected!');
});
console.log(emitter.listenersAny()[0]); // [ [Function] ]

emitter.emit(event | eventNS, [arg1], [arg2], [...])

Execute each of the listeners that may be listening for the specified event name in order with the list of arguments.

emitter.emitAsync(event | eventNS, [arg1], [arg2], [...])

Return the results of the listeners via Promise.all. Only this method doesn't work IE.

emitter.on('get',function(i) {
  return new Promise(function(resolve){
    setTimeout(function(){
      resolve(i+3);
    },50);
  });
});
emitter.on('get',function(i) {
  return new Promise(function(resolve){
    resolve(i+2)
  });
});
emitter.on('get',function(i) {
  return Promise.resolve(i+1);
});
emitter.on('get',function(i) {
  return i+0;
});
emitter.on('get',function(i) {
  // noop
});

emitter.emitAsync('get',0)
.then(function(results){
  console.log(results); // [3,2,1,0,undefined]
});

emitter.waitFor(event | eventNS, [options])

emitter.waitFor(event | eventNS, [timeout])

emitter.waitFor(event | eventNS, [filter])

Returns a thenable object (promise interface) that resolves when a specific event occurs

emitter.waitFor('event').then(function (data) { 
    console.log(data); // ['bar']
});

emitter.emit('event', 'bar');
emitter.waitFor('event', { 
    // handle first event data argument as an error (err, ...data)
    handleError: false,
    // the timeout for resolving the promise before it is rejected with an error (Error: timeout).
    timeout: 0, 
    //filter function to determine acceptable values for resolving the promise.
    filter: function(arg0, arg1){ 
        return arg0==='foo' && arg1==='bar'
    },
    Promise: Promise, // Promise constructor to use,
    overload: false // overload cancellation api in a case of external Promise class
}).then(function(data){
    console.log(data); // ['foo', 'bar']
});

emitter.emit('event', 'foo', 'bar')
var promise= emitter.waitFor('event');

promise.then(null, function(error){
    console.log(error); //Error: canceled
});

promise.cancel(); //stop listening the event and reject the promise
emitter.waitFor('event', {
    handleError: true
}).then(null, function(error){
    console.log(error); //Error: custom error
});

emitter.emit('event', new Error('custom error')); // reject the promise

emitter.eventNames(nsAsArray)

Returns an array listing the events for which the emitter has registered listeners.

var emitter= new EventEmitter2();
emitter.on('foo', () => {});
emitter.on('bar', () => {});
emitter.on(Symbol('test'), () => {});
emitter.on(['foo', Symbol('test2')], () => {});

console.log(emitter.eventNames());
// Prints: [ 'bar', 'foo', [ 'foo', Symbol(test2) ], [ 'foo', Symbol(test2) ] ]

Note: Listeners order not guaranteed

listenTo(targetEmitter, events: event | eventNS, options?)

listenTo(targetEmitter, events: (event | eventNS)[], options?)

listenTo(targetEmitter, events: Object<event | eventNS, Function>, options?)

Listens to the events emitted by an external emitter and propagate them through itself. The target object could be of any type that implements methods for subscribing and unsubscribing to its events. By default this method attempts to use addListener/removeListener, on/off and addEventListener/removeEventListener pairs, but you able to define own hooks on(event, handler) and off(event, handler) in the options object to use custom subscription API. In these hooks this refers to the target object.

The options object has the following interface:

  • on(event, handler): void
  • off(event, handler): void
  • reducer: (Function) | (Object<Function>): Boolean

In case you selected the newListener and removeListener options when creating the emitter, the subscription to the events of the target object will be conditional, depending on whether there are listeners in the emitter that could listen them.

var EventEmitter2 = require('EventEmitter2');
var http = require('http');

var server = http.createServer(function(request, response){
    console.log(request.url);
    response.end('Hello Node.js Server!')
}).listen(3000);

server.on('connection', function(req, socket, head){
   console.log('connect');
});

// activate the ability to attach listeners on demand 
var emitter= new EventEmitter2({
    newListener: true,
    removeListener: true
});

emitter.listenTo(server, {
    'connection': 'localConnection',
    'close': 'close'
}, {
    reducers: {
        connection: function(event){
            console.log('event name:' + event.name); //'localConnection'
            console.log('original event name:' + event.original); //'connection'
            return event.data[0].remoteAddress==='::1';
        }
    }
});

emitter.on('localConnection', function(socket){
   console.log('local connection', socket.remoteAddress);
});

setTimeout(function(){
    emitter.stopListeningTo(server);
}, 30000);

An example of using a wildcard emitter in a browser:

const ee= new EventEmitter2({
   wildcard: true
});

ee.listenTo(document.querySelector('#test'), {
   'click': 'div.click',
   'mouseup': 'div.mouseup',
   'mousedown': 'div.mousedown'
});

ee.on('div.*', function(evt){
    console.log('listenTo: '+ evt.type);
});

setTimeout(function(){
    ee.stopListeningTo(document.querySelector('#test'));
}, 30000);

stopListeningTo(target?: Object, event: event | eventNS): Boolean

Stops listening the targets. Returns true if some listener was removed.

hasListeners(event | eventNS?:String):Boolean

Checks whether emitter has any listeners.

emitter.listeners(event | eventNS)

Returns the array of listeners for the event named eventName. In wildcard mode this method returns namespaces as strings:

var emitter= new EventEmitter2({
    wildcard: true
});
emitter.on('a.b.c', function(){});
emitter.on(['z', 'x', 'c'], function(){});
console.log(emitter.eventNames()) // [ 'z.x.c', 'a.b.c' ]

If some namespace contains a Symbol member or the nsAsArray option is set the method will return namespace as an array of its members;

var emitter= new EventEmitter2({
    wildcard: true
});
emitter.on('a.b.c', function(){});
emitter.on(['z', 'x', Symbol()], function(){});
console.log(emitter.eventNames()) // [ [ 'z', 'x', Symbol() ], 'a.b.c' ]

EventEmitter2.once(emitter, event | eventNS, [options])

Creates a cancellable Promise that is fulfilled when the EventEmitter emits the given event or that is rejected when the EventEmitter emits 'error'. The Promise will resolve with an array of all the arguments emitted to the given event. This method is intentionally generic and works with the web platform EventTarget interface, which has no special 'error' event semantics and does not listen to the 'error' event.

Basic example:

var emitter= new EventEmitter2();

EventEmitter2.once(emitter, 'event', {
    timeout: 0,
    Promise: Promise, // a custom Promise constructor
    overload: false // overload promise cancellation api if exists with library implementation
}).then(function(data){
    console.log(data); // [1, 2, 3]
});

emitter.emit('event', 1, 2, 3);

With timeout option:

EventEmitter2.once(emitter, 'event', {
    timeout: 1000
}).then(null, function(err){
    console.log(err); // Error: timeout
});

The library promise cancellation API:

promise= EventEmitter2.once(emitter, 'event');
// notice: the cancel method exists only in the first promise chain
promise.then(null, function(err){
    console.log(err); // Error: canceled
});

promise.cancel();

Using the custom Promise class (bluebird.js):

var BBPromise = require("bluebird");

EventEmitter2.once(emitter, 'event', {
    Promise: BBPromise
}).then(function(data){
    console.log(data); // [4, 5, 6]
});

emitter.emit('event', 4, 5, 6);
var BBPromise = require("bluebird");

BBPromise.config({
    // if false or options.overload enabled, the library cancellation API will be used
    cancellation: true 
});

var promise= EventEmitter2.once(emitter, 'event', {
    Promise: BBPromise,
    overload: false // use bluebird cancellation API
}).then(function(data){
    // notice: never executed due to BlueBird cancellation logic
}, function(err){
    // notice: never executed due to BlueBird cancellation logic
});

promise.cancel();

emitter.emit('event', 'never handled');

EventEmitter2.defaultMaxListeners

Sets default max listeners count globally for all instances, including those created before the change is made.

eventemitter2's People

Contributors

agirorn avatar ash042 avatar atesgoral avatar badsyntax avatar colonelbundy avatar digitalbrainjs avatar fresheneesz avatar gitawego avatar heapwolf avatar indexzero avatar indutny avatar jamesonjlee avatar jasonkuhrt avatar kadirpekel avatar kwiateusz avatar marsup avatar mfferreira avatar mmalecki avatar moeriki avatar piuccio avatar pwellner avatar rangermauve avatar robertjustjones avatar sebakerckhof avatar sergeyksv avatar shama avatar simenb avatar stanleytakamatsu avatar tauren avatar willian-zhang avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

eventemitter2's Issues

CoffeeScript compatibility

Instantiating a CoffeeScript class defined by new class extends EventEmitter2

throws

TypeError: Cannot read property 'constructor' of undefined

I am aware that utilising pure prototypal inheritance is a goal of this library. However, is there a way this approach can co-exist with CoffeeScript inheritance?

For your reference, the following JavaScript is the compilation of new class extends EventEmitter2

var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) {
  for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; }
  function ctor() { this.constructor = child; }
  ctor.prototype = parent.prototype;
  child.prototype = new ctor;
  child.__super__ = parent.prototype;
  return child;
};
new ((function() {
  __extends(_Class, EventEmitter2);
  function _Class() {
    _Class.__super__.constructor.apply(this, arguments);
  }
  return _Class;
})());

setMaxEventListeners: 0 for unlimited not working

setting 0 as setMaxListeners always gets overridden by the default value, since assignment is like this:
var m = this._events.maxListeners || defaultMaxListeners;
and 0 is considered falsy.

since the check that controls wether to show a warning or not is m > 0, -1 is a valid argument to setMaxListeners to prevent warnings, since it isn't considered falsy, but this isn't reflected in the documentation

newListener event doesn't fire if wildcard option is true

If wildcard option is set to true a newListener event doesn't fire.

Here's a test:

var EventEmitter2 = require('../lib/node_modules/eventemitter2').EventEmitter2;

var x = new EventEmitter2({ wildcard: false });
x.on('newListener', function(){
    console.log('this line appears');
});
x.on('test', function(){ });


var y = new EventEmitter2({ wildcard: true });
y.on('newListener', function(){
    console.log('this line should appear too, but it\'s not');
});
y.on('test', function(){ });

emit() returns undefined when it should return true

emit() should return true in the following test case:

var EventEmitter = require('eventemitter2').EventEmitter2;
var ee = new EventEmitter();

ee.onAny(function() {
    console.log("handled");
});

console.log("emit returned " + ee.emit("foo"));

Execution order with wildcard listeners

Setup:

var server = new EventEmitter2({
  delimiter: '.',
  wildcard: true
});

server.on('foo.*', function () {
  console.log('foo.*');
});

server.on('foo.bar', function () {
   console.log('foo.bar');
});


server.emit('foo.bar');

Expected (in the console):

foo.*
foo.bar

Actual:

foo.bar
foo.*

I expect the listener for foo.* to be executed before foo.bar because it is added first.

Is this by design, or is this a bug? You can also see a live version here: http://jsbin.com/IsUJEB/1/edit.

Thank you!

Inconsistent handling of error events by wildcard observers

When there is no 'error' event listener, wildcard event listeners do not observe 'error' events and the process is killed, e.g.

var EventEmitter2 = require('eventemitter2').EventEmitter2;

var emitter = new EventEmitter2({
  wildcard: true
, delimiter: ':'
, newListener: false
});

emitter.on('*', function () {
  console.log('not reached');
});

emitter.emit('error', new Error());
console.log('not reached');

This script will not reach either of the console.log statements because the unhandled 'error' event causes the program to exit.

On the other hand, when there is an 'error' event handler, the wildcard observer also observes the 'error' event, e.g

var EventEmitter2 = require('eventemitter2').EventEmitter2;

var emitter = new EventEmitter2({
  wildcard: true
, delimiter: ':'
, newListener: false
});

emitter.on('*', function () {
  console.log('wildcard handler called');
});

emitter.on('error', function () {
  console.log('error handler called');
});

emitter.emit('error', new Error());
console.log('reached');

In this script, both both the 'error' handler and the '*' handler observe the error event, and all console.log statements are reached.

It would be much more consistent to either:

  • Explicitly specify that a wildcard observer will handle error events (and thus not cause the program to exit).
  • Explicitly specify that the special nature of error events means that they will not ever be handled by wildcard observers.

[Feature Request] All unhandled events

Hey Paolo

I'd love to do a kind of pipe to another EventEmitter for all events that haven't been handled by my component.

The API I have in mind is the following:

server.on('foo.*', function(value1, value2) {
  console.log(this.event, value1, value2);
}).on('bar.baz', function(value1, value2) {
  console.log(this.event, value1, value2);
}).unhandled(function(value1, value2) {
  // Any event not in 'foo.*' or 'bar.baz':
  console.log(this.event, value1, value2); 
});

unhandled could be onOther or whatever else fits into the EventEmitter style.

What do you think, could this be low-level enough to be useful for everyone else?

Implement 'before' and 'after' emitter aspects?

Would it make sense to add emitter.before and emitter.after so that we can bind methods to these aspects of events?

The API would look like:

emitter.on('sup::dog', function(){
  console.log('hello');
});

emitter.before('sup::dog', function(args){
  // indicates that 'sup::dog' event will not fire
  return false;
});

emitter.on('sup::cat', function(){
  console.log('hello');
  // indicates after event will not fire if bound
  return false;
});

emitter.after('sup::cat', function(){
  console.log('hello');  
});

Expected Behavior?

Is the statements below not expected to work?
Currently they don't for 'master'. Am i missing something?

// basic.js

var EventEmitter2 = require('../EventEmitter2.js').EventEmitter2;


exports.issue1 = function (test) {
  var e = new EventEmitter2();

  e.on('foo.bar.baz', function (event, value) {
    test.equals('foo.bar.baz', event);
    test.equals('somevalue', value);
    test.done();
  });

  e.emit('foo.bar.baz', 'somevalue');
}


exports.issue2 = function (test) {
  var e = new EventEmitter2();

  e.on('foo.*', function (event, value) {
    test.equals('foo.bar.baz', event);
    test.equals('somevalue', value);
    test.done();
  });

  e.emit('foo.bar.baz', 'somevalue');
}

// output
/*
$ nodeunit basic.js 

basic.js
✖ issue1

AssertionError: 'foo.bar.baz' == [ 'foo', 'bar', 'baz' ]
    at Object.equals (/Users/kadir/node/lib/node_modules/nodeunit/lib/types.js:81:39)
    at EventEmitter2.<anonymous> (/Users/kadir/EventEmitter2/test/basic.js:10:10)
    at EventEmitter2.emit (/Users/kadir/EventEmitter2/EventEmitter2.js:94:20)
    at /Users/kadir/EventEmitter2/test/basic.js:15:5
    at Object.runTest (/Users/kadir/node/lib/node_modules/nodeunit/lib/core.js:54:9)
    at /Users/kadir/node/lib/node_modules/nodeunit/lib/core.js:90:21
    at /Users/kadir/node/lib/node_modules/nodeunit/deps/async.js:508:13
    at /Users/kadir/node/lib/node_modules/nodeunit/deps/async.js:118:13
    at /Users/kadir/node/lib/node_modules/nodeunit/deps/async.js:134:9
    at /Users/kadir/node/lib/node_modules/nodeunit/deps/async.js:507:9


FAILURES: Undone tests (or their setups/teardowns): 
- issue2

To fix this, make sure all tests call test.done()
*/

EventEmitter2 cannot be compiled with closure's advanced compilation

It should only be a slight change to make it compatible with advanced complation of google's javascript compiler. This could result in significant reductions in size (80% or so).

The main problem seems to be the way EventEmitter2 is made global in the browser. This is not properly detected by Closure, causing the object to be renamed while construction calls are not.

An example (test this at http://closure-compiler.appspot.com/home):

// ==ClosureCompiler==
// @compilation_level ADVANCED_OPTIMIZATIONS
// @output_file_name default.js
// ==/ClosureCompiler==

;!function(root, undefined) {

  var EventEmitter2 = root.EventEmitter2 = function EventEmitter2(conf) {

    // If *_listeners* is undefined invoke *initializeEvents* to set the
    // default properties.
    if (!this._listeners) this.initializeEvents(conf);
  };

  EventEmitter2.prototype.initializeEvents = function(conf) {
    conf = conf || {};
    var self = this;

    conf.delimiter = conf.delimiter || '.';

    if (conf.delimiter === '*' && conf.delimiter.length === 1) {
      throw new Error('initializeEvents doesn\'t accept a "*" (wild-card) character as delimiter');
    } else {
      self._delimiter = conf.delimiter;
    }

    self._caseSensitive = typeof conf.caseSensitive === 'undefined' ? true : conf.caseSensitive;
    self.setMaxListeners(conf.maxListeners);
    self._listeners = {};
    self._allListenerFn = [];
    return self;
  }

  EventEmitter2.prototype.addListener = function(event, fn) {
    var self = this;

    // If *fn* is not a function throw an error. An *fn* that is not a function
    // can not be invoked when an event is emitted and therefor is not allowed
    // to be added.
    if (typeof fn !== 'function') {
      throw new Error('addListener only accepts instances of Function');
    }

    // If *_listeners* is undefined invoke *initializeEvents* to set the
    // default properties.
    if (!self._listeners) self.initializeEvents();

    // Set variables.
    var i = 0, l = 0,
        delimiter = self._delimiter,
        doubleDelimiter = delimiter + delimiter,
        caseSensitive = self._caseSensitive,
        listeners = self._listeners,
        maxListeners = self._maxListeners,
        event = caseSensitive ? event : event.toLowerCase(),
        namespaced = ~event.indexOf(delimiter),
        ns, exploreNs = listeners,
        listenToEvent, eventListeners;

    // If the event is namespaced loop through the namespaces, set seperate
    // events for each namespace and set *listenToEvent* to the last
    // namespace to attach the listener function to this event after the loop.
    if (namespaced) {

      // If an event starts or ends with a delimiter character or two or more
      // delimiter characters are used after each other throw an error.
      // Starting or ending an event with a delimiter character or combining
      // characters creates empty namespaces which don't work and therefor are
      // not allowed.
      if (event[event.length - 1] === delimiter || event[0] === delimiter || ~event.indexOf(doubleDelimiter)) {
        self.eventDelimiterError('addListener');
        return false;
      }

      // Split the event into a namespace array for looping.
      ns = event.split(delimiter);

      // Loop through the namespaces.
      for (i = 0, l = ns.length; i < l; i++) {

        // If the namespace contains a wildcard character check if this
        // character is the only one in the namespace. If it is not the only
        // character in the namespace this namespace is invalid and therefor
        // an error is thrown.
        if (ns[i].indexOf('*') !== -1 && ns[i].length !== 1) {
          self.eventWildcardError('addListener');
          return false;
        }

        // If the event is undefined in *exploreNs* it means it doesn't exist
        // in *listeners* so a new event should be created.
        if (!exploreNs[ns[i]]) {
          exploreNs[ns[i]] = {
              _name: ns[i],
              _fn: [],
              _ns: {}
            };
        }

        // If the loop is at the end set *listenToEvent* to the current
        // namespace - which is the last one - to attach the listener to this
        // event. If the loop is not at the end rebase *exploreNs* to loop the
        // current event's namespaces.
        if (i === ns.length - 1) {
          listenToEvent = exploreNs[ns[i]];
        } else {
          exploreNs = exploreNs[ns[i]]._ns;
        }
      }
    }

    // If the event is not namespaced set the single event and set
    // *listenToEvent* to this event to attach the listener to.
    else {

      // If the event contains a wildcard character check if this
      // character is the only one in the event. If it is not the only
      // character in the event this event is invalid and therefor
      // an error is thrown.
      if (event.indexOf('*') !== -1 && event.length !== 1) {
        self.eventWildcardError('addListener');
        return false;
      }

      // If the event is undefined in *listeners* it means it doesn't exist
      // so a new event should be created.
      if (!listeners[event]) {
        listeners[event] = {
          _name: event,
          _fn: [],
          _ns: {}
        };
      }

      // Set *listenToEvent* to the current event to attach the listener to.
      listenToEvent = listeners[event];
    }

    eventListeners = listenToEvent._fn;

    // Signal that a new listener is being added.
    self.emit('newListener', event, fn);

    // If the max amount of listeners has been reached signal and return to
    // cancel the addition of this new listener.
    if (maxListeners > 0 && eventListeners.length >= maxListeners) {
      self.emit('maxListeners', event);
      return self;
    }

    // Add the function to the event listener collection.
    eventListeners.push(fn);
    return self;
  };

  EventEmitter2.prototype.onAny = function(fn) {

    // If *fn* is not a function throw an error. An *fn* that is not a function
    // can not be invoked when an event is emitted and therefor is not allowed
    // to be added.
    if (typeof fn !== 'function') {
      throw new Error('onAny only accepts instances of Function');
    }

    // If *_listeners* is undefined invoke *initializeEvents* to set the
    // default properties.
    if (!this._listeners) this.initializeEvents();

    // Add the function to the event listener collection.
    this._allListenerFn.push(fn);
  };

  EventEmitter2.prototype.on = EventEmitter2.prototype.addListener;

  EventEmitter2.prototype.once = function(event, fn) {
    this.many(event, 1, fn);
    return this;
  };

  EventEmitter2.prototype.many = function(event, ttl, fn) {
    var self = this;

    // If *fn* is not a function throw an error. An *fn* that is not a function
    // can not be invoked when an event is emitted and therefor is not allowed
    // to be added.
    if (typeof fn !== 'function') {
      throw new Error('many only accepts instances of Function');
    }

    function listener() {
      if (--ttl == 0) {
        self.removeListener(event, listener);
      }
      fn.apply(null, arguments);
    };
    listener._origin = fn;

    self.addListener(event, listener);

    return self;
  };

  EventEmitter2.prototype.emit = function(event) {
    var self = this,
        args = arguments,
        i = 0, l = 0,
        delimiter = self._delimiter,
        doubleDelimiter = delimiter + delimiter,
        caseSensitive = self._caseSensitive,
        listeners = self._listeners,
        event = caseSensitive ? event : event.toLowerCase(),
        namespaced = ~event.indexOf(delimiter),
        ns, exploreNs = [listeners], collectedListeners = [], collectedErrors = false,
        invoked = false;

    // If no listeners are defined the emit will not be able to invoke a
    // function. Therefore return *invoked* to exit.
    if (!listeners) return invoked;

    // If the event is namespaced loop through the namespaces, find all the
    // listeners for the namespaces and wildcards and add them to the
    // *collectedListeners* array for listener invocation after the loop.
    if (namespaced || event === '*') {

      // If an event starts or ends with a delimiter character or two or more
      // delimiter characters are used after each other throw an error.
      // Starting or ending an event with a delimiter character or combining
      // characters creates empty namespaces which don't work and therefor are
      // not allowed.
      if (event[event.length - 1] === delimiter || event[0] === delimiter || ~event.indexOf(doubleDelimiter)) {
        self.eventDelimiterError('emit');
        return false;
      }

      // Split the event into a namespace array for looping.
      ns = event.split(delimiter);

      // Loop through the namespaces.
      for (i = 0; i < ns.length; i++) {

        // If the namespace contains a wildcard character check if this
        // character is the only one in the namespace. If it is not the only
        // character in the namespace this namespace is invalid and therefor
        // an error is thrown.
        if (ns[i].indexOf('*') !== -1 && ns[i].length !== 1) {
          self.eventWildcardError('addListener');
          return false;
        }

        // While looping through the namespace array loop through *exploreNs*
        // with *i* as well. This is basically the same as looping through the
        // the different levels of *events*. *collectedNs* is used to collect
        // all the namespaces that are going to be explored for the next level
        // of *events*.
        var currentNs = ns[i],
            currentExploreNs = exploreNs[i],
            collectedNs = [];

        // Loop through the current level of *events*.
        for (var key1 in currentExploreNs) {

          // Set the current namespace in *events* that is being explored.
          var exploredNs = currentExploreNs[key1],
              name = caseSensitive ? exploredNs._name : exploredNs._name.toLowerCase();

          // If there is a match for the namespace or a wildcard collect the
          // next level of namespaces or collect the listeners of this
          // particular namespace.
          if (currentNs === '*' || (name === currentNs || name === '*')) {

            // If the loop is at the end collect the listeners for this
            // particular event and add them to the *collectedListeners* array
            // for listener invocation after the loop. If the loop is not at
            // the end collect the next level of namespaces.
            if (i === ns.length - 1) {
              var listeners = exploredNs._fn;

              if (listeners.length > 0) {
                collectedListeners = collectedListeners.concat(listeners);
              }
            } else {
              for (var key2 in exploredNs._ns) {
                collectedNs.push(exploredNs._ns[key2]);
              }
            }
          }

          // ...
          if (currentNs === 'error' && name === 'error') {
            collectedErrors = true;
          }
        }

        // ...
        if (currentNs === 'error' && !collectedErrors && (!self._allListenerFn || self._allListenerFn.length === 0)) {

          // ...
          if (arguments[1] instanceof Error) {
            throw arguments[1];
          } else {
            throw new Error("Uncaught, unspecified 'error' event.");
          }

          return invoked;
        }

        exploreNs.push(collectedNs);
      }
    }

    // If the event is not namespaced collect all the functions for this
    // particular event and from the wildcard event and add them to
    // the *collectedListeners* array for invocation.
    else {

      // If the event contains a wildcard character check if this
      // character is the only one in the event. If it is not the only
      // character in the event this event is invalid and therefor
      // an error is thrown.
      if (event.indexOf('*') !== -1 && event.length !== 1) {
        self.eventWildcardError('addListener');
        return false;
      }

      // ...
      if (event === 'error') {

        // ...
        if (
            !listeners ||
            !listeners['error'] || listeners['error']._fn.length === 0 &&
            (!self._allListenerFn || self._allListenerFn.length === 0)
          ) {

          // ...
          if (arguments[1] instanceof Error) {
            throw arguments[1];
          } else {
            throw new Error("Uncaught, unspecified 'error' event.");
          }

          return invoked;
        }
      }

      if (listeners[event] && listeners[event]._fn.length > 0) {
        collectedListeners = collectedListeners.concat(listeners[event]._fn);
      }

      if (listeners['*'] && listeners['*']._fn.length > 0) {
        collectedListeners = collectedListeners.concat(listeners['*']._fn);
      }
    }

    // Loop through the collected functions and invoke them.
    if (collectedListeners.length > 0) {
      for (i = 0, l = collectedListeners.length; i < l; i++) {
        collectedListeners[i].apply(self, args);
        invoked = true;
      }
    }

    // Loop through the *_allListenerFn* functions and invoke them.
    if (self._allListenerFn && self._allListenerFn.length > 0) {
      for (i = 0, l = self._allListenerFn.length; i < l; i++) {
        self._allListenerFn[i].apply(self, args);
        invoked = true;
      }
    }

    return invoked;
  };

  EventEmitter2.prototype.emitAll = function() {};

  EventEmitter2.prototype.removeListener = function(event, fn) {
    var i = 0, l = 0, fns,
        names, ns = this._listeners;
    event = this._caseSensitive ? event : event.toLowerCase();

    // fn not being a function does nothing 
    if (~event.indexOf(this._delimiter)) {
      // namespaced, so try to find the ns
      names = event.split(this._delimiter);
      for (i = 0, l = names.length; i < l; i++) {
        if (!ns || !ns[names[i]]) {
          // if this namespace was never defined, return
          return this;
        }
        if (i === names.length-1){
          ns = ns[names[i]];
        } else {
          ns = ns[names[i]]._ns;
        }
      }
      if (!ns._fn) {
        // if this ns is empty for some reason
        return this;
      }
      // above we stop 1 _ns before the last one
      // so we havet go INTO the _ns, and grab it's _ns
      fns = ns._fn;
      for(var i = 0, l = fns.length; i < l; i++) {
       if(fn === fns[i] || fn === fns[i]._origin) {
          fns.splice(i, 1);
          return this;
        }
      }
    } 
    else {
      if (ns[event]) {
        // if this is not a namespaced event, just remove
        fns = ns[event]._fn;
        for (var i = 0, l = fns.length; i < l; i++) {
          if(fn === fns[i] || fn === fns[i]._origin) {
            fns.splice(i, 1);
            return this;
          }
        }
      }
   }
    // if we get here just return
    return this;
  };

  EventEmitter2.prototype.un = EventEmitter2.prototype.removeListener;

  EventEmitter2.prototype.unAny = function(fn) {
    var i = 0, l = 0, fns;
    if (fn && this._allListenerFn && this._allListenerFn.length > 0) {
      fns = this._allListenerFn;
      for(i = 0, l = fns.length; i < l; i++) {
        if(fn === fns[i]) {
          fns.splice(i, 1);
          return this;
        }
      }
    } else {
      this._allListenerFn = [];
    }
    return this;
  };

  EventEmitter2.prototype.removeAllListeners = function(event) {
    var listeners, i, l;
    // if there is an event, then remove every listener on that listener
    if (!event) {
      this._listeners = {};
      this._allListenerFn = [];
    }
    else {
      listeners = this.listeners(event);
      for (i = 0, l = listeners.length; i < l; i++) {
        this.removeListener(event, listeners[i]);
      }
    }
    return this;
  };

  EventEmitter2.prototype.setMaxListeners = function(n) {
    this._maxListeners = n === 0 ? 0 : n || 10;
    return this;
  };

  EventEmitter2.prototype.listeners = function(event) {
    var ns = this._listeners,
        i = 0, l = 0, fns,
        names,
        toReturn;
    // case sensititivty
    event = this._caseSensitive ? event : event.toLowerCase();
    // check if we are namespaced
    if (~event.indexOf(this._delimiter)) {
      // namespaced so, find it
      names = event.split(this._delimiter);
      for (i = 0, l = names.length; i < l; i++) {
        if (!ns || !ns[names[i]]) {
          // if this namespace was never defined, return
          return [];
        }
        if (i === names.length-1){
          ns = ns[names[i]];
        } else {
          ns = ns[names[i]]._ns;
        }
      }
      return ns._fn || [];
    }
    else {
      if (!ns[event]) {
        return [];
      }
      return ns[event]._fn || [];
    }
  };

  EventEmitter2.prototype.listenersAny = function() {
    return this._allListenerFn;
  }

  EventEmitter2.prototype.eventDelimiterError = function(fn) {
    throw new Error(fn + ' doesn\'t accept empty namespaces or events starting or ending with a "' + this._delimiter + '" (delimiter) character');
  };

  EventEmitter2.prototype.eventWildcardError = function(fn) {
    throw new Error(fn + ' doesn\'t accept events starting, ending or containing a "*" (wildcard) character');
  };

}(typeof exports === 'undefined' ? window : exports);

//now create an emitter
var emitter = new EventEmitter2();

Results in the following output:

(function(b){b=b.f=function(a){this.b||this.d(a)};b.prototype.d=function(a){a=a||{};a.a=a.a||".";if(a.a==="*"&&a.a.length===1)throw Error('initializeEvents doesn\'t accept a "*" (wild-card) character as delimiter');else this.i=a.a;this.h=typeof a.c==="undefined"?!0:a.c;this.e(a.k);this.b={};this.g=[]};b.prototype.e=function(a){this.j=a===0?0:a||10}})(typeof exports==="undefined"?window:exports);new EventEmitter2;

Obviously, the call new EventEmitter2; should become new f;.

git tag releases so they are visible to bower

To make a release for bower one simply needs to push a git tag to github. I see that npm registry has all versions 0.4.12 and 0.4.13 for example, but these are missing from bower, because tags are not pushed to github.

Feature Request: parallel onMulti

Let's say I want to run my server when it has been configured and when socketio has been configured.

This could be done like so:

ee2.once('server.configured', function(server) {
    ee2.once('socketio.configured', function(io) {
        server.listen(3000);
    });
});

Just giving some random example. Now, it is possible that socketio.configured fires before server.configured fires. In this case, the above would not work. Thus I propose a new API that allows one to catch parallel events (that can be emitted in any order). Eg.:

ee2.onceMulti('server.configured', 'socketio.configured', function(server, io) {
    server.listen(3000);
});

Any remarks?

Avoid calling 'this.emit' when 'this.newListener' is false

Instead of always calling this.emit from EventEmitter.prototype.on and making the check for this.newListener in the emit method, wouldn't it be better just to make a conditional call to this.emit from EventEmitter.prototype.on only if this.newListener is true?. Eg, replacing line 356 by:

if (this.newListener === true) {
    this.emit('newListener', type, listener);
} 

Maybe I'm missing something, but the current behaviour may be strange for someone extending EventEmitter2 and the emit method, as I was doing :-)

Regards.

Question: How do I emit errors in the constructor

If I have an EventEmitter and I want to emit an error in the constructor, how do I listen for an error event?

For instance:

new MyEmitter().on('error', function () {});

Since the error handler isn't bound until after the constructor finishes, the error event isn't handled and exits the program.

Is there a way to either:

  1. queue the error event to be handled after the constructor finishes
  2. set up an error handler at the time of construction

CC: @crayo

Test Suite

Added test skeleton, needs to have tests written.

allow event.on('*', ...)

Be able to listen to every possible event without having to include a namespace.

currently you have to event.on(':',...) but should be able to event.on('*',...)

EventEmitter.listeners("*") does not return all listeners

EventEmitter.listeners("") does not return all listeners. The wildcard should expand to all events. Currently, EventEmitter.listeners("") acts like EventEmitter.listenersAny(). If that is the desired approach, EventEmitter.listenersAny() is redundant and users can use EventEmitter.listeners("*").

However, EventEmitter.listeners("*") could return all listeners. And EventEmitter.listenersAny() should probably be deprecated and replaced with EventEmitter.listenersOnAny(), a more intuitive naming and avoiding redundancy.

IMHO

EventEmitter.listeners() errors when no arguments passed and wildcards enabled

EventEmitter2.listeners() errors when no arguments passed and wildcards enabled:

Error:

TypeError: Cannot call method 'slice' of undefined
at EventEmitter.listeners (/home/leonspencer/repos/node->cloud/node_modules/eventemitter2/lib/eventemitter2.js:561:77)

Code(eventemitter2.js):
if(this.wildcard) {
var handlers = [];
var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice();
searchListenerTree.call(this, handlers, ns, this.listenerTree, 0);
return handlers;
}

Consider checking for 'typeof type === undefined' might be an approach:

if(this.wildcard) {
  var handlers = [];
  if('typeof type === undefined') {
     searchListenerTree.call(this, handlers, "*", this.listenerTree, 0);
  } else {
     var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice();
     searchListenerTree.call(this, handlers, ns, this.listenerTree, 0);
  }      
  return handlers;
}

Listening once on array

This is similar to #31 but distinct.
Can be feature request.

Look at code below.

var EventEmitter2 = require('eventemitter2').EventEmitter2;
var game = new EventEmitter2;\

function endGame() { console.log('THE END'); }

game.once(['player:quit', 'player:disconnect'], function () { endGame() });

//this does nothing - but should call endGame and unregister it
game.emit('player:quit');

//thid does nothing - but should'n call endGame because of previous call
game.emit('player:disconnect');

// this call endGame - this give no sense to me
game.emit(['player:quit', 'player:disconnect']);

I believe an array given to once shold be handled as one of given. There is no sense to listen to given array.

Calling event listeners separately, instead of calling them sequentially

Hello,

What do you think of not calling event listeners sequentially in the same JavaScript event processing thread using apply in a for loop? E.g. https://github.com/hij1nx/EventEmitter2/blob/master/EventEmitter2.js#L90

The possible downside of the current approach is that if the first listener throws an exception, the other listeners won't be called, which sucks. I can imagine eventEmitters being used in apps where different listeners originate from different sources/authorities and thus it doesn't makes sense to punish listeners that work correctly for the mischief of a single misbehaving listener.

Perhaps a better solution would be to invoke each listener separately by wrapping listeners in closures and invoking them separately using setTimeout(listener, 0)? That way - if a single listener throws, the others are safe since they will be processed as a separate JS timout event.

Just thinking out loud...

Thanks!

code needs cleanup,

some local variables are actually globals (this actually introduces a bug).

style needs to be corrected.

should have some more comments.

Documentation Improvements

EventEmitters have the ability to become complex and even confusing, documentation should provide a set of best practices to properly control growth/complexity. This section could address the Do's and Dont's of event emitters.

Proposal: `.has()` to determine if an instance has a given event listener

Right now given the flat data structure used by the core EventEmitter it is easy to detect if it has a listener for a given event:

e.g. https://github.com/nodejitsu/node-http-proxy/blob/master/lib/node-http-proxy.js#L765

OR

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

  if (!emitter._events || emitter._events['some-event-name].length === 0) {
    console.log('emitter does not have listeners for `some-event-name`');
  }

The more complex data structure that EventEmitter2 introduces makes this more complex. Would be nice to have something similar

handling of bad namespaces

Currently there is no error handling for badly namespaced events such as:

event.ns1.
event..ns2
.ns1
.

as it stands it just eats the messages because there is no _event or _listeners associated with ''
It should throw an error.

Please bump version number and push to npm

Current version on npm fails to install on NPM version 1.2.30

npm install eventemitter2
npm http GET https://registry.npmjs.org/eventemitter2
npm http 304 https://registry.npmjs.org/eventemitter2
npm WARN package.json [email protected] No repository field.
npm WARN package.json [email protected] 'repositories' (plural) Not supported.
npm WARN package.json Please pick one as the 'repository' field
npm WARN package.json [email protected] No repository field.

Ability to specify listener order

Sometimes two listeners will bind to an event, where the execution order of the listeners matters.

Are there any plans to add support for listener order in EventEmitter2?

So my thoughts on how this can be accomplish is:

  • Specify an order paramater when listening to events - e.g. .on(listenerName, [listenerPriority,] listenerCallback)
  • Specify an order configuration object that explains what listeners must run before other listeners - e.g. listenerOrder: {'eventName': ['pluginNameThatShouldExecuteFirst', 'pluginNameThatShouldExecuteLast']}

Thoughts?

event name is passed to every listener...

Not sure why this has been done, but for some reason every event name is now being passed to the listener passed to on:

 //create an event emitter for this app
var emitter = new EventEmitter2(),
    events = {
        rawTwitterDataFetched: 'rawTwitterDataFetched' //fired when raw data has been fetched from twitter
    };

//handle incoming raw twitter data
emitter.on(events.rawTwitterDataFetched, function(data) {
    console.log('should handle data: %o, arguments %o', data, arguments);
});

$.getJSON(generateRequestURL(), function(data) {
    if (data) emitter.emit(events.rawTwitterDataFetched, data);
});

This logs should handle data: "rawTwitterDataFetched", arguments ["rawTwitterDataFetched", Object]

Note I'm running this in the browser.

What happend here?

Listening with a wildcard on once and emitting a certain event

I would expect this listener to run only once, but it isn't removed after fired:

var type = 'test1.foo.bar';
var type2 = 'test1.foo.*';
var functionA = function() { test.ok(true, 'Event was fired'); };

emitter.once(type2, functionA);
emitter.emit(type);
emitter.emit(type);

test.expect(1);

This case is very similar to to unit test number 7 here: https://github.com/hij1nx/EventEmitter2/blob/master/test/wildcardEvents/ttl.js but the type and type2 are interchanged.

Is this behaviour intentional?

memory leak when using unique listener keys

Memory leak when using unique listener keys

Description

When somebody is using unique listeners for e.g. rpc-calls the EventEmitter2 implementation is leaking memory like hell. But when using static listener keys all works great.

The following test shows that the implementation is leaking memory very fast when using unique listener keys. After a few seconds the node process will need hundreds of MB. But when using static listener keys by changing leaksMemory to false the needed memory will stay constant.

Testcode

var leaksMemory = true;

/**
 * reproducible test code
 */

var cnt =  0;
function uniqid() {
    return (leaksMemory) ? (++cnt) : 'static';
}

var emitter = new (require('eventemitter2').EventEmitter2)({
    wildcard: true,
    delimiter: '.',
    maxListeners: 100
});

emitter.on('rpc-adder', function rpcResponder(rpcResponseId, numA, numB) {
    emitter.emit(rpcResponseId, numA + numB);
});

function rpcClient() {
    var rpcResponseId = 'rpc.' + uniqid();
    var numA = Math.random(), numB = Math.random();

    emitter.once(rpcResponseId, function rpcReceiveResponse(numResult) {
        //console.log(numA + ' + ' + numB + ' = ' + numResult);
        process.nextTick(rpcClient);
    });
    emitter.emit('rpc-adder', rpcResponseId, Math.random(), Math.random());
}

rpcClient();

Description of testcode

The uniqid() function will generate a unique key when leaksMemory == true to show the leaking of memory, when it's false static keys are used to show that leaking only occurs by using unique keys.
The function rpcClient() is a rpc client creating a responseid it listens for receiving the rpc result. It'll publish a rpc function call with eventname rpc-adder and subscribes for the answer. The eventname for the answer is a unique key to prevent eventname collisions of concurrent rpc calls by many clients. After every response a new rpc client call is started by using process.nextTick to have a new call stack and make tons of rpc calls in a short time to quickly reproduce the problem.

Unexpected behavior with wildcards

I was trying to do the following :

      var EventEmitter = require('eventemitter2').EventEmitter2;

      var e = new EventEmitter();


      e.on('i.*', function(){
        console.log('hit i* event');
      });


      e.on('i.foo', function(){
        console.log('hit i.foo');
      });

      e.emit('i.foo');

      // outputs: hit i.foo

I expected this emit would be captured by both listeners. Is that wrong?

maxListeners parameter not respected in constructor

While you can use setMaxListeners to change the maximum number of listeners, the same constructor parameter is ignored. The API documentation shows that we should be able to do

    var server = new EventEmitter2({
      maxListeners: 20
    });
}

But the configure function does nothing with it

function configure(conf) {
    if (conf) {
      conf.delimiter && (this.delimiter = conf.delimiter);
      conf.wildcard && (this.wildcard = conf.wildcard);
      if (this.wildcard) {
        this.listenerTree = new Object;
      }
    }
  }

inconsistancy in reademe

at the top of the reademe the event is being passed to listener the as the first argument,
but later it's only the value is being passed (although it is being ignored)

also, I notice that this is not backwards compatible with the old event emitter, because the old event emitter does not pass the event name as the first argument.

but great stuff!

wanted: unconstrained `this`

Hi!

From docu:

server.on('foo.*', function(value1, value2) {
  console.log(this.event, value1, value2);
});

Am I right that i can't supply bound function as handler, since this is used to carry event stuff. Wouldn't it be better to not rely on this and don't constrain use cases? Event per se can be passed as the first parameter.

TIA,
--Vladimir

Errata on README.md

In the readme, under "Differences", I noticed:

var server = EventEmitter2({    // <------------ here
    wildcard: true, // should the event emitter use wildcards.
    delimiter: '::', // the delimiter used to segment namespaces, defaults to `.`.
    maxListeners: 20, // the max number of listeners that can be assigned to an event, defaults to 10.
});

is missing a "new" keyword. I tried it without the new keyword and it didn't work, so I'm guessing this is a typo?

emit() returns true when it should return false

In the following test emit() should return false

var EventEmitter = require('eventemitter2').EventEmitter2;
var ee = new EventEmitter({wildcard: true});

ee.on("foo", function() {
    console.log("handled");
});

console.log("emit returned " + ee.emit("bar"));

This can be fixed by changing line 312 of eventemitter2.js to return listeners.length > 0;

there is no emitAll method

not really sure I understand what this method would do, but it's in the documentation and not in the code

Globstar

Are there any plans to support a globstar wildcard in events? For example emitter.on(['foo', '**'], cb), for any event that starts with foo, and may have zero or more sub-namespaces, or even emitter.on(['**', 'foo', '**'], cb), for any event that contains foo. I've read in #72 that you're planning on making the wildcard system pluggable. Is that still relevant, and will it be possible to have globstar functionalty by those means?

Not compatible with stitch

I'm trying to use EE2 with stitch, and in order to make it work on the browser I had to add window.process = { "title": "hello world"} before issuing the require statement.

Why checking the window object existance is not enough?

Mixing in EventEmitter2

The native Node.js EventEmitter can be mixed in or used as inheritor for other objects. Using the constructor of EventEmitter2 to initialize and setup some of the basic properties prevents that from being possible (as far as my knowledge goes).

In the examples in the documentation you are using EventEmitter2 as the base object to "create" a new server, this however is not a real server but just an EventEmitter so that's why you probably haven't run into this issue yet :)

Looking into the core API's of Node.js, the native EventEmitter is inherited with a utility function to basically copy it's properties / functions to the other object. For an example see:

https://github.com/joyent/node/blob/master/lib/http.js#L1142

I would like to be able to do this with EventEmitter2 as well.

Do you agree if I fix EventEmitter2 so it can be used as the native EventEmitter and be mixed in with other objects using the utility function?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.