Giter VIP home page Giter VIP logo

eventemitter2's Issues

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.

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

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!

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?

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"));

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?

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.

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.

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.

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?

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?

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.

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;
}

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

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;
      }
    }
  }

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

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;.

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?

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?

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.

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?

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?

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?

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('*',...)

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.

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()
*/

[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?

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;
})());

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.

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.

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;

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!

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

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

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

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');  
});

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(){ });

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.