Skip to content

Loading…

Event Emitter #1436

Closed
wants to merge 2 commits into from
@0x00A

Hello All. Here is the eventEmitter that I've been working on. I have added a few simple features, none of which are breaking changes. These changes (namespace/wildcards etc) have been documented in doc/api/events.js. This code is passing all release tests.

@ry, as discussed there does not seem to be a noticeable increase in performance of the ab tests despite the performance increase of the javascript in isolation.

@ry
ry commented

do you have tests?

benchmarks appear to be on par with HEAD

nits: there's a few EOL whitespace errors. you should wrap the lines in the docs.

@0x00A

I have fairly comprehensive tests, but they are written using nodeunit. i'm not sure how you guys feel about that. They can be found here (https://github.com/hij1nx/EventEmitter2/tree/master/test). I can refactor the dependancy out of these tests if you want to integrate them into the project.

@mikeal
Node.js Foundation member

v8 inlines the constructor for new MyObject() which would explain the perf difference.

@mikeal
Node.js Foundation member

+1 to this patch including tests.

I'm +0 on the names of things ;) My immediate reaction to the *Any names and .un() is negative but to be honest I can't think of a better name so I don't have any room to complain.

@ry
ry commented

@hij1nx we'll need tests using the assert module - but let's see if we actually want this before making you do a bunch of work.

@pquerna, @koichik, @bnoordhuis, @felixge, @isaacs what are your opinions?

@mikeal
Node.js Foundation member

I'm +0 on namespaces.

+1 on the catchall listener, there was a thread about that a while ago that was pretty unanimous in favor of adding it.

@isaacs

I'll review the code changes more closely in the next day or two. Feature-wise, it all looks good to me. I'm glad we're going about this carefully, this path is hot like magma.

@indexzero
Node.js Foundation member

+1 Clearly; obviously I'm a little baised since I helped write it :-D

@isaacs This pull-request is the result of a lot of concerted effort from @hij1nx @drjackal and myself, the magma will flow in the hot paths (i.e. w/o { wildcard: true }) as it did before

@mikeal Namespaces are really useful, have you taken a look at how they are used in hook.io@0.6.0?

@mikeal
Node.js Foundation member

I know namespaces can be useful, it's not a -1, it's a +0. It's something I won't use but don't dislike and generally don't have a strong opinion about one way or the other :)

@bnoordhuis
Node.js Foundation member

-1

Namespaces: on the fence, convince me.

onMany: somewhat useful, I suppose, but something you can stitch together yourself with little effort.

Wildcards: this has been hashed out on the mailing list but the tl;dr version is that no one could present a good use case except debugging and that's something you can duct-tape together in 2 minutes.

function log_events(ee) {
  var fun = ee.emit;
  ee.emit = function() {
    console.error(arguments);
    return fun.apply(ee, Array.prototype.slice.call(arguments));
  };
  return ee;
}

All in all it seems like a lot of extra code for little gain.

@indutny
Node.js Foundation member

+1
I thought about namespaces since my acquaintance with nodejs. And wildcards... Oh, I can't even describe how cool it'll be to see that in core.

@indexzero
Node.js Foundation member

@bnoordhuis Have you taken a look at how hook.io@0.6.0 works? It takes serious advantage of the wildcard events. There is a recent post on the Nodejitsu blog which I suggest reading to get a better understanding of the benefits here:

http://blog.nodejitsu.com/distribute-nodejs-apps-with-hookio

@mikeal
Node.js Foundation member

the question is less about how useful it would be to third party modules and more about what we would use it for in core. if there isn't a single usecase for it in core then it probably doesn't belong.

cool stuff doesn't have to be in core, in fact there is an argument to be made that more cool stuff in third party modules is a better thing for the community long term.

that said, a listener for all events has been brought up many times as a need for debugging.

@bnoordhuis
Node.js Foundation member

@indexzero: Okay, I see how wildcards and namespaces can be useful. But I agree with @mikeal that it's something that core doesn't have any use for (right now anyway) so it should stay in user land. Let's say -0.5 instead of -1.

@jamesonjlee

You can do the namespacing with current EE, foo::bar is just a simple string, the wildcard feature is what is new. foo::*
or even better. *::error

@0x00A

@bnoordhuis @mikeal -- I am a proponent of a sparse core <3

But I also believe namespaces are a succinct and well known mechanism for event characterization. As we build more complex event based systems using node, we should establish simple and well known conventions for describing them. This patch provides basic progressive enhancement that does not impose upon the existing node programming style or APIs. Therefore I'm compelled to propose it as a core commit.

@coverslide

As long as there's no breakage, and no major performance penalty, I don't see why it shouldn't be in core. It is incredibly useful, and the simple matter that core modules don't use it doesn't mean they can't benefit from them in the future.

@mikeal
Node.js Foundation member

@hij1nx "As we build more complex event based systems using node".

That's the issue though isn't it. This is intended for use by people using node, not by core itself.

It's not that I don't think this pattern is a good one, but adding it would be blessing it. We're trying not to do that.

Blessing a particular pattern can reduce innovation on top of node.js in the rest of the community, and it's still very early.

If this patch made it in hardly anyone would use your eventemitter2 library because it's primary offerings will be in core. That library is where most of the current innovation is happening around the patterns you propose here, if nobody is using it where does that innovation happen?

And if in 6 months you have a great idea that compliments or potentially changes some of this stuff you won't have a lot of users to give you feedback and you'll just be trying to put more of it in to core and it'll be even harder and hit considerably more skepticism.

@tj
tj commented

+0 from me. Doesn't really matter to me either but there are plenty of "nice to have"s that could be in core and gain wider adoption, a better sprintf implementation, blah blah.

@mranney

I really like EventEmitter, and I think it's a useful abstraction and guide for how to write node programs. I like these improvements, but I'm torn about whether I think they should go in core. I would probably not use this if it weren't in core, but I probably would if it were. I think the real question is whether we think this is how people should be writing node programs. By putting it in core, it basically says, "you should do it this way."

For listeners that can catch multiple events, it'd be useful if they were passed the original event name that matched.

@0x00A

The EventEmitter2 library came from a need, the need to be more expressive when interacting with events. Since node APIs make heavy use of EventEmitter. I believe this is less endorsing a pattern than maturing an existing one.

@mranney for the case where a wildcard is used, this.event will provide the actual name of the event fired.

@codenothing
function MyObject(params){
    EventEmitter.call(this);
    this.params = params;
    this.init();
}

util.inherits(MyObject, EventEmitter);
MyObject.prototype.init = function(){
    // Do something to initialize your object
};

I personally don't like the the above example, but I see a lot of third-party modules blindly eventing their objects. I think there are too many namespaces included in this commit, and there might be a number of unknown conflicts with older modules.

@0x00A

The EventEmitter2 library came from a need, the need to be more expressive when interacting with events. Since node APIs make heavy use of EventEmitter, I believe this is less endorsing a pattern than maturing an existing one.

@mranney for the case where a wildcard is used, this.event will provide the actual name of the event fired.

Edit: @mikeal i've been intentionally quite conservative with this work, and i will continue to be, i think it should stay simple.

@codenothing you lost me with " I think there are too many namespaces included in this commit, and there might be a number of unknown conflicts with older modules." =)

@isaacs

@hij1nx I think the issue is that "init" is a fairly common method name, and calling "this.init()" might not call EventEmitter.prototype.init. How badly does it affect the benchmarks to change that to EventEmitter.prototype.init.call(this);, or just inlining the "init" functionality into the EventEmitter constructor?

@isaacs

(that is, "the issue" = "the thing that @codenothing is complaining about". Please correct me if I missed the point of your comment, @codenothing.)

@0x00A

his example assumes you need to call it, you actually should never need to call init(), init gets called for you...

this.events || this.init();

Edit: it has become quite clear to me over time that people are often unsure how inherits works with relationship to constructors, hence the above code occurs when needed to reconcile the problem. /cc @isaacs @codenothing

@indexzero
Node.js Foundation member

I was reluctant to bring this up because it is clearly part of a larger discussion. My apologies to all for such hubris.

From what I've gleaned from @ry, a real goal for core is to be able to facilitate somekind of distributed communication between node processes. Clearly the .fork() API is a step in this direction. Working with peer processes and overall how a supervision tree (or perhaps undirected / directed graph) of processes would communicate is unclear. It is true that namespaces are not always necessary, for things such as data, request, connection events, but having been working intimately with a distributed network of node processes recently I see true value (within core) in the semantics that wildcard, and namespaced events introduced.

Specific replies

@mikeal We have a blessed version of IPC in .fork(), I'm not questioning that implementation or the decision to have it in core, but I think that this EventEmitter would provide patterns necessary to really leverage the existing core IPC APIs. I'm never one to want to stifle innovation, but the features here are not revolutionary: it performs a trie-like exploration of an n-ary tree with arbitrary depth (i.e. Object literal) keyed on components of an event name. We had tried alternative implementations that were proven to be less performant, if you're curious I have a gist of that discussion: https://gist.github.com/1036944. (Also see my comment to @mranney)

@mranney Why would you not use this if it wasn't in core? The separate EventEmitter2 module will continued to be maintained by @hij1nx and the rest of Nodejitsu because it adds some sugar for using it in the browser that doesn't belong in node for those who don't want to use something like browserify.

@hij1nx I believe that @isaacs @codenothing are correct. To my understanding when the MyObject constructor function is invoked, the init() method has already been overridden on the "child" prototype, so the call to this.init and (less likely) the call to this.setConf will invoke the methods on the "child", not the methods on EventEmitter.prototype as intended. Given that init and setConf are somewhat common names, we could invoke the prototype or indicate the reserved status through _init and _setConf as is with the HTTP _setHeader and _renderHeaders API
hij1nx/node@edfe18f#L0R23

@codenothing

Sorry, should have been more clear. What I'm getting at is there is potential for many naming conflicts.

  • init
  • setConf
  • defaultMaxListeners
  • wildcard
  • delimiter
  • listenerTree
  • many
  • event
  • un
  • unAny

Are all new properties created with this commit. For such a highly extended object, there are bound to be naming conflicts within existing modules. I like the addition, it just needs to be well documented.

@0x00A

@indexzero @codenothing assumed because init was in the ctor that he would have to call it, but that is not how it works. #coderead /cc @isaacs

@mikeal
Node.js Foundation member

Is usefulness, by itself, a reason to be in core?

I don't think we've answered that question well before and it seems to be the source of our contention.

@mjackson

-1 for including micro-optimization in the constructor and choosing instead to use this._events || this.init() every time you call the much more frequently called addListener, emit, listeners, etc.

I sincerely hope that this pattern of concealed initialization doesn't make it any further into Node core. As it is, many core constructors already use the following pattern:

function MyConstructor(options) {
  if (!(this instanceof MyConstructor)) {
    return new MyConstructor(options);
  }
}

If you're feel the need to compensate for others' lack of understanding constructors, you should use the same pattern.

@isaacs

@hij1nx Enough of these "describing a program in English" hijinks. To the coderator!

var EE = require("events").EventEmitter

function MyThing () {
  EE.call(this);
}

MyThing.prototype = Object.create(EE.prototype,
                                  { constructor: { value: MyThing }})

MyThing.prototype.init = function () {
  throw "uh oh!"
}

MyThing.prototype.setConf = function (conf) {
  assert.equal(conf, 9, "The number 9 is the only valid configuration!")
}

new MyThing() // uh oh! (also, invalid non-9 configuration)

That's what I was getting at. EventEmitter is extended quite often. We can rely on the ctor being called, but we can't necessarily rely on a method with a name as innocuous as "init" not being overridden.

@0x00A

@isaacs ah i see =) trivial rename.

@mikeal has the best input so far. Even though his ultimate point could be considered a little existential ;) My final stance is that i believe it brings maturity to an existing pattern.

@mjijackson, did you ever read the existing code? this is the original approach as well.

@indexzero
Node.js Foundation member

@mikeal I think the answer to your questions "Is usefulness, by itself, a reason to be in core?" should be no. Adding anything that is simply by itself useful would have node.js end up as a high-level web framework, which is not the direction of the project. I do, however, believe the real question here to be different.

Is usefulness towards node's long term goals, by itself, a reason to be in core?

To me, the addition of Windows support and .fork() are both useful suggest the answer to this question is yes.

Empirically, there are additions that fall on both sides of the original question. Overall (based on a triage of closed pull requests) , additions seem to fix bugs / edge cases, or better implement a common RFC. I do not pretend to know all of the long term goals, but I do think that this addition is worthwhile.

@isaacs

@hij1nx Yes, but an unnecessary speed-bump. What's the performance impact of doing EventEmitter.prototype.init.call(this) instead of this.init(), or inlining that functionality in the ctor? ('setConf` is more specific and less likely to be messed with.)

I'm not in principle opposed to expanding the size of the EventEmitter surface, but it should be done as conservatively as possible, and no conservativelier. In my opinion, init crosses the line into the realm of high collision risks.

@mikeal et al

Is usefulness, by itself, a reason to be in core?

This is a great question. In my opinion, if you're going to put an event system in a programming platform (and put it in the tagline, even!), then it makes sense to have a damn fine one built in by default.

We tend to focus a lot on the "io" part, and that's great. The unified Stream interface is a perfect example of something that is very generic and useful, and makes it easier (and more fun) to write programs that manipulate event-driven IO.

You could write network programs in node before, but they were larger and less readable than necessary, and involved a lot of re-invention. Clearly none of us needed a unified interface for HTTP and file streaming, but once we had it, it opened up a lot of doors for abstractions that we hadn't considered before.

I think that this set of features is covered by a similar precedent.

@0x00A

@isaacs, after a little reflection, init() should not be user accessible, the user should never call it, its a flaw to expose it. @mjijackson makes a good point about the instance check, thats another way to handle it. Funny, I think how i did it was carry-over from the old implementation. I usually use the instance check for this sort of thing. Even though the current implementation is just a bool check instance check might even buy some performance.

@ry
ry commented

if only javascript had pattern matching...

@tim-smart

-1

Seems something that should stay user-land until core needs it. Makes it easier for people to innovate and iterate as mikeal pointed out.

The current event emitter is a simple, no-frills, gets-the-job-done constructor. It fits the current core philosophy quite well and encourages people to look to the community for extra functionality.

PS: Needless to say EventEmitter2 is a great piece of work.

@0x00A

@Tim-Smart -1'd it, and i think tim smart is quite smart. @mikeal +0'd it. so unless @ry, @felixge or @pquerna +1 it i'm going to -1 it >=)

@mranney

After reading the discussion and thinking about it for a while, I'm going to cast my vote as +1.

We didn't NEED to put in a connection pooling HTTP client, but I'm glad we did. It was a bit of a rough road, but I think we are just about through, and node will be the better for it. I think these EE changes are like that. I'll bet there will be some mysterious new bugs that arise from this, but I think we'll end up with a system that's more useful to more people.

@chjj

Sorry, I don't usually comment on pull requests, but I feel I have to comment on namespaces.

Personally, when I see something getting added to core, I like to judge it by asking:

  1. Will it be used internally?
  2. Does it perform some common or essential task, inherent to networking or IO?
  3. Would a moderate amount of user modules actually use it?

If the answer is yes to at least one of those, I think it might be a good idea, and tend to agree with the change.

For event namespaces, the answers to #1 and #2 are no. That leaves us with #3, which I think holds the least merit on its own, if answered yes.

So I'm wondering, how many user modules would actually make use of namespaces if they were implemented? How many modules use EE2 right now? It just doesn't seem like something that essential.

@0x00A

@chjj, tabs we're not essential to browsing. but they are pretty nice to have.

@chjj

@hij1nx, browser tabs fulfilled a very common task though. People very commonly would open multiple browser windows in the days before tabs. Tabs solved this, and pretty much everyone uses them.

@Marak

+1

I'm using EE2 right now for a few projects.

At first, my knee-jerk reaction was that this shouldn't be in core. After a lot of consideration, I changed my mind.

Enabling proper namespaced events in core is a HUGE win for user-land. I feel strongly that if we can add this without slowing down performance, it will help developers out, A LOT.

@koichik

I am -1.
In this patch, wildcard is an option and disabled by default.
So we cannot use wildcard on an object which was provided from Node core modules such as http.
I think it does not have to be in Node core.
I may be +1 if wildcard was enabled by default without penalties.

@0x00A

@chjj maybe that was a big comparison, it was fun to do. but in any case, its a very common task to want to organize various forms of information. for instance, i want to divide my files into directories, my pictures into albums my friends into circles, my event names into segments =)

@tj
tj commented

unless node does adopt this sort of pattern for IPC, I dont see the point, we could say json-rpc style is helpful and start including that as well, it never ends!

@0x00A

@koichik has a very good point, hands down the best point so far (if we're judging based on the idea that these features are only useful if they are internally useful) [[respectfully, this is not a sarcastic remark]]

@visionmedia agreed, but that is somewhat exaggerated example right? i mean, this is a very very small change that extends an existing pattern in a pretty subtle way (in contrast to your example)

@tj
tj commented

@hij1nx haha yeah definitely a bit different. It's a bit of a tough call, saying not to include something unless node core uses it doesn't really work either because there are still quite a few things core it-self doesn't really provide. I guess the major thing is that if it can be done reasonably as a third-party module or not. It's cool it just seems like a similar to something like the request module where (in most cases at least) is just extending and the API additions are still optional but where do you draw that line. I dont know a good answer for that either, just seems like var EventEmitter = require('ee2') or something isn't much of a problem

@0x00A

@visionmedia you raise some great points. var EventEmitter = require('ee2') is not much of a problem, that's currently how its used.

As a community, I believe we are motivated, not just to keep core sparse, but to keep the mental model simple and let growth emerge at the perimeter. So I believe the question in regards to this patch is centered around how disruptive these changes are to the mental model of node developers and how it affects innovation potential. It feels like either an executive decision at this point, or at least a core committers decision.

@aredridel

Wow. Good stuff. My gut reaction to wildcards is "I'm not going to enjoy a lot of what people do with them", so +0 from me on that, but namespaces feel relatively right.

@rauchg

-1 on changing one of the core-est parts of node for something that hasn't really seen overwhelming adoption or interest.
+1 on waiting longer and reviewing this again.

@mraleph

@mikeal constructor inlining is not implemented yet. only normal calls are inlined by crankshaft.

@Gozala

-1 on making this part of core until there is an actual need for these features in the core itself.

Making this part of the core puts all other alternative patterns into a very weak position, which I don't think is beneficial for the community, at least not yet.

@0x00A

@Gozala the idea that something is only useful if its useful internally, is itself debatable as we've already discussed. Adding value to user-land also has validity.

@Gozala

@hij1nx

@Gozala the idea that something is only useful if its useful internally, is itself debatable as we've already discussed.

That's why I'm expressing my opinion on that.

I just think in general it's better to defer blessing any particular pattern until that becomes a blocker (core needs it).

@0x00A

@Gozala, of course its fair to state your case! but again, i do not believe this patch is a blessing, i believe it lends maturity and completeness to the existing pattern. Having named events and being able to classify them are not such disparate ideas.

@indexzero
Node.js Foundation member

I'd like to elaborate on the scenario that I outlined in my previous comments in the interest of both those now joining the comment thread, and solidifying a high-level thought into a concrete example. That is, what does the current .fork() IPC mechanism look like once it becomes more widely used and what are scenarios that we want to support?

If you have not seen @isaacs talk on "Node digs DIRT", now would be a good time.

I've prepared a sample centered around DIRT / ETL based applications for which node has clear limitations from the V8 heap. If we want to support these scenarios, this should go into core. In my opinion, this isn't some abstract user-land problem, this is grunt-work data processing that we need to be capable of doing:

https://gist.github.com/1122318

@aheckmann

-1 The only feature here that I recall wanting is namespaced events and that only one time for a very specific use case.

+1 For leaving it in userland.

@0x00A

damn. @indexzero presents a very compelling argument. a lot of -1's feel nullified.

@felixge

-1 on merging this as a whole.

This is too big of a change to be reasonably discussed by the amount of people involved in this discussion. We need to split this effort into multiple patches which we can discuss separately, one after another.

I therefor suggest that we focus on the catch-all listener feature first. I feel like:

  • Namespaces and wildchars can be implemented on top of it in userland
  • We probably have more core people who would agree to it than for the other feature proposals
  • A core use case would be to forward unknown events in .pipe() (let's not discuss that for now, it's merely a potential example)

--fg

@0x00A

I agree with @felixge. this is a big change. It needs to be broken into smaller parts. There is a lot of knee-jerk reaction and a lot of noise in this thread. This needs to go to a smaller group.

@0x00A 0x00A closed this
@isaacs

Ok, had a chance to read through the changes more carefully. I think that this is overall a very good patch, and the benchmarks seem to be a bit faster than on master, which is always nice. The code is very understandable, and seems to work as documented.

Some relatively minor issues:

  1. Prefer named functions. That is function growListenerTree(type, listener) { rather than var growListenerTree = function(type, listener) {. Makes stack traces nicer.
  2. Some trailing spaces that should be cleaned up. Certainly not the only make jslint offense in node's codebase, but it'd be good to reduce those rather than increase :)
  3. EventEmitter.prototype.init should not be exposed (discussed above).
  4. Seems like onAny should return this, to match the pattern of once, on, etc.
  5. I agree that it would be nice to split into a few commits if it can be done in a nice atomic way, but I don't think that's such a big deal. It's not that big, really.
  6. I'd really like it if we could just pick a delimiter and wildcard, rather than making that configurable. Everyone I've seen using EventEmitter2 seems to prefer ":" as the delimiter and "*" as the wildcard, and it seems like the natural fit.
@ry
ry commented

I'm +1 ... I think.

@slaskis

I've personally always liked the namespaces in jQuerys events but I guess it might be a bit limited as it assumes a certain style?

But an example could be:

// Add two events namespaced to 'http' 
server.on("connection.http",onConnect).on("close.http",onClose) 
// Removed all events namespaced with 'http'
server.removeEventListener(".http") 

I guess it's the ":" (or even worse "::") that looks like dirty xml namespaces to me when the "." looks more like clean js/json to me...but I thought I'd just throw this into the mix

@chjj

Namespaces aside, if this does go in eventually, I have one small suggestion. Rename the removeListener alias, un to off. It corresponds to on rather nicely, and if you know what on does, you could figure out what off does. un seems kind of unintuitive.

@tj
tj commented

@chjj++

@mikeal
Node.js Foundation member

this bikeshedding is now out of control.

i think @felixge is right, we need to break this up in to a few different patches, it would make this a lot easier.

@0x00A

@mikeal, i agree. but I also dont agree. @isaacs, i get on those changes immediately. @ry let's put the patch in =)

@chjj & @visionmedia this is actually a very common short cut in browser frameworks, thats why i adopted it, but i also really like off.

@0x00A

@slaskis . is the default delimiter, which is configurable. @isaacs, I agree, perhaps you or @ry could just make that decision.

@tj
tj commented

really? I've never seen .un(). I agree with the guys though, the patch is a little unfocused and could easily be broken up. The amount of talk in here really shows how badly we need some kind of description for what gets in core though haha, whatever that is.

@slaskis

@hij1nx haha, yeah, after posting (shame on me) I saw that in the diffs too.

Another difference is the implicit wildcard i.e. ".http" instead of "*.http". Might be slightly harder to read perhaps, but it sure looks clean if you ask me...

@0x00A

@visionmedia with all due respect, i whole heartedly disagree that it is unfocused, in fact i believe it to be exactly the opposite. It is laser focused on the expressiveness of events, specifically how someone will be able to characterize them.

@tj
tj commented

@hij1nx as a whole, but each method etc could be a commit that people could individually be accepted/commented on etc

@0x00A

@visionmedia well, the commit is actually pretty small, we're talking about + 394 additions, less after my newest optimizations/simplifications (shows another performance improvement at master).

@mikeal
Node.js Foundation member

size of this discussion is dwarfing the size of the commit. breaking it up reduces the surface area for discussion :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Aug 1, 2011
Commits on Aug 2, 2011
This page is out of date. Refresh to see the latest.
Showing with 394 additions and 97 deletions.
  1. +77 −15 doc/api/events.markdown
  2. +317 −82 lib/events.js
View
92 doc/api/events.markdown
@@ -12,6 +12,24 @@ Functions can then be attached to objects, to be executed when an event
is emitted. These functions are called _listeners_.
+**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(['foo.bazz']);
+ emitter.emit(['foo', 'bar']);
+
+
### events.EventEmitter
To access the EventEmitter class, `require('events').EventEmitter`.
@@ -29,31 +47,63 @@ added.
Adds a listener to the end of the listeners array for the specified event.
+
server.on('connection', function (stream) {
console.log('someone connected!');
});
+
+#### emitter.onAny(listener)
+
+Adds a listener that will be fired when any event is emitted.
+
+ function f(value) {
+ console.log('This event will be listened to exactly four times.');
+ };
+
+ server.onAny(f);
+
+
+#### emitter.unAny(listener)
+
+Removes the listener that will be fired when any event is emitted.
+
+
+ server.unAny(f);
+
+
#### emitter.once(event, listener)
-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.
+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.
- server.once('connection', function (stream) {
- console.log('Ah, we have our first user!');
+
+ server.once('connection', function (value) {
+ console.log('Ah, we have our first value!');
+ });
+
+
+#### emitter.many(event, timesToListen, listener)
+
+Adds a listener that will execute **n times** for the event before being removed. The listener is invoked only the first time the event is fired, after which it is removed.
+
+
+ server.many('connection', 4, function (value) {
+ console.log('Ah, we have captured a user!');
});
+
#### emitter.removeListener(event, listener)
+#### emitter.un(event, listener)
+
+Remove a listener from the listener array for the specified event. **Caution**: changes array indices in the listener array behind the listener.
-Remove a listener from the listener array for the specified event.
-**Caution**: changes array indices in the listener array behind the listener.
var callback = function(stream) {
console.log('someone connected!');
};
server.on('connection', callback);
// ...
- server.removeListener('connection', callback);
+ server.un('connection', callback);
#### emitter.removeAllListeners([event])
@@ -63,28 +113,40 @@ 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.
+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.listeners(event)
-Returns an array of listeners for the specified event. This array can be
-manipulated, e.g. to remove listeners.
+Returns an array of listeners for the specified event. This array can be manipulated, e.g. to remove listeners.
+
server.on('connection', function (stream) {
console.log('someone connected!');
});
+
console.log(util.inspect(server.listeners('connection')); // [ [Function] ]
+
+#### emitter.listenersAny(event)
+
+Returns an array of listeners that are listening for any event that is specified. This array can be manipulated, e.g. to remove listeners.
+
+
+ server.onAny(function(value) {
+ console.log('someone connected!');
+ });
+
+ console.log(console.log(server.listenersAny()[0]); // [ [Function] ] // someone connected!
+
+
#### emitter.emit(event, [arg1], [arg2], [...])
-Execute each of the listeners in order with the supplied arguments.
+Execute each of the listeners that may be listening for the specified event name in order with the list of arguments.
#### Event: 'newListener'
`function (event, listener) { }`
This event is emitted any time someone adds a new listener.
+
View
399 lib/events.js
@@ -19,31 +19,195 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
-var isArray = Array.isArray;
+var EventEmitter = function EventEmitter(conf) {
+ this.init();
+ this.setConf(conf);
+};
-function EventEmitter() { }
exports.EventEmitter = EventEmitter;
+EventEmitter.prototype.init = function() {
+ this._events = new Object; // faster in v8
+ this.defaultMaxListeners = 10;
+};
+
+var isArray = Array.isArray;
+
// 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.
-var defaultMaxListeners = 10;
+
+EventEmitter.prototype.setConf = function(conf) {
+ this.wildcard = conf && conf.wildcard;
+ this.delimiter = conf && conf.delimiter || '.';
+ if(this.wildcard) {
+ this.listenerTree = new Object;
+ }
+};
+
EventEmitter.prototype.setMaxListeners = function(n) {
- if (!this._events) this._events = {};
+ this._events || this.init();
this._events.maxListeners = n;
};
+EventEmitter.prototype.event = '';
+
+var searchListenerTree = function(handlers, type, tree, i) {
+ if (!tree) {
+ return
+ }
+
+ var listeners;
+
+ if (i === type.length && tree._listeners) {
+ //
+ // If at the end of the event(s) list and the tree has listeners
+ // invoke those listeners.
+ //
+ if (typeof tree._listeners === 'function') {
+ handlers && handlers.push(tree._listeners);
+ return tree;
+ } else {
+ for (var leaf = 0, len = tree._listeners.length; leaf < len; leaf++) {
+ handlers && handlers.push(tree._listeners[leaf]);
+ }
+ return tree;
+ }
+ }
+
+ if (type[i] === '*' || tree[type[i]]) {
+ //
+ // If the event emitted is '*' at this part
+ // or there is a concrete match at this patch
+ //
+ if (type[i] === '*') {
+ for (var branch in tree) {
+ if (branch !== '_listeners' && tree.hasOwnProperty(branch)) {
+ listeners = searchListenerTree(handlers, type, tree[branch], i+1);
+ }
+ }
+ return listeners;
+ }
+
+ listeners = searchListenerTree(handlers, type, tree[type[i]], i+1);
+ }
+
+
+ if (tree['*']) {
+ //
+ // If the listener tree will allow any match for this part,
+ // then recursively explore all branches of the tree
+ //
+ searchListenerTree(handlers, type, tree['*'], i+1);
+ }
+
+ return listeners;
+};
+
+var growListenerTree = function(type, listener) {
+
+ type = typeof type === 'string' ? type.split(this.delimiter) : type.slice();
+
+ var tree = this.listenerTree;
+ var name = type.shift();
+
+ while (name) {
+
+ if (!tree[name]) {
+ tree[name] = new Object;
+ }
+
+ tree = tree[name];
+
+ if (type.length === 0) {
+
+ if (!tree._listeners) {
+ tree._listeners = listener;
+ }
+ else if(typeof tree._listeners === 'function') {
+ tree._listeners = [tree._listeners, listener];
+ }
+ else if (isArray(tree._listeners)) {
+
+ tree._listeners.push(listener);
+
+ if (!tree._listeners.warned) {
+
+ var m = this.defaultMaxListeners;
+
+ if (m > 0 && tree._listeners.length > m) {
+
+ tree._listeners.warned = true;
+ console.error('(node) warning: possible EventEmitter memory ' +
+ 'leak detected. %d listeners added. ' +
+ 'Use emitter.setMaxListeners() to increase limit.',
+ tree._listeners.length);
+ console.trace();
+ }
+ }
+ }
+ return true;
+ }
+ name = type.shift();
+ }
+ return true;
+};
+
+EventEmitter.prototype.once = function(event, fn) {
+ this.many(event, 1, fn);
+ return this;
+};
+
+EventEmitter.prototype.many = function(event, ttl, fn) {
+ var self = this;
+
+ if (typeof fn !== 'function') {
+ throw new Error('many only accepts instances of Function');
+ }
+
+ function listener() {
+ if (--ttl === 0) {
+ self.un(event, listener);
+ }
+ fn.apply(null, arguments);
+ };
+
+ listener._origin = fn;
+
+ this.on(event, listener);
+
+ return self;
+};
EventEmitter.prototype.emit = function() {
+ this._events || this.init();
+
var type = arguments[0];
+ this.event = type;
+
// If there is no 'error' event listener then throw.
+
+ if (type === 'newListener') {
+ if (!this._events.newListener) { return false; }
+ }
+
+ // Loop through the *_allListenerFn* functions and invoke them.
+ if (this._all) {
+ var l = arguments.length;
+ var args = new Array(l - 1);
+ for (var i = 1; i < l; i++) args[i - 1] = arguments[i];
+ for (i = 0, l = this._all.length; i < l; i++) {
+ this._all[i].apply(this, args);
+ }
+ }
+
if (type === 'error') {
- if (!this._events || !this._events.error ||
- (isArray(this._events.error) && !this._events.error.length))
- {
+ if (!this._events.error || typeof this._events.error !== 'function' &&
+ typeof this._events.error[0] !== 'function') {
+
if (arguments[1] instanceof Error) {
throw arguments[1]; // Unhandled 'error' event
} else {
@@ -53,32 +217,39 @@ EventEmitter.prototype.emit = function() {
}
}
- if (!this._events) return false;
- var handler = this._events[type];
- if (!handler) return false;
+ var handler;
- if (typeof handler == 'function') {
- switch (arguments.length) {
- // fast cases
- case 1:
- handler.call(this);
- break;
- case 2:
- handler.call(this, arguments[1]);
- break;
- case 3:
- handler.call(this, arguments[1], arguments[2]);
- break;
- // slower
- default:
- var l = arguments.length;
- var args = new Array(l - 1);
- for (var i = 1; i < l; i++) args[i - 1] = arguments[i];
- handler.apply(this, args);
+ if(this.wildcard) {
+ handler = [];
+ var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice();
+ searchListenerTree.call(this, handler, ns, this.listenerTree, 0);
+ }
+ else {
+ handler = this._events[type];
+ }
+
+ if (typeof handler === 'function') {
+ if (arguments.length === 1) {
+ handler.call(this);
}
+ else if (arguments.length > 1)
+ switch (arguments.length) {
+ case 2:
+ handler.call(this, arguments[1]);
+ break;
+ case 3:
+ handler.call(this, arguments[1], arguments[2]);
+ break;
+ // slower
+ default:
+ var l = arguments.length;
+ var args = new Array(l - 1);
+ for (var i = 1; i < l; i++) args[i - 1] = arguments[i];
+ handler.apply(this, args);
+ }
return true;
-
- } else if (isArray(handler)) {
+ }
+ else if (handler) {
var l = arguments.length;
var args = new Array(l - 1);
for (var i = 1; i < l; i++) args[i - 1] = arguments[i];
@@ -88,43 +259,46 @@ EventEmitter.prototype.emit = function() {
listeners[i].apply(this, args);
}
return true;
-
- } else {
- return false;
}
-};
-// EventEmitter is defined in src/node_events.cc
-// EventEmitter.prototype.emit() is also defined there.
-EventEmitter.prototype.addListener = function(type, listener) {
- if ('function' !== typeof listener) {
- throw new Error('addListener only takes instances of Function');
- }
+};
- if (!this._events) this._events = {};
+EventEmitter.prototype.on = function(type, listener) {
+ this._events || this.init();
// To avoid recursion in the case that type == "newListeners"! Before
// adding it to the listeners, first emit "newListeners".
this.emit('newListener', type, listener);
+ if(this.wildcard) {
+ growListenerTree.call(this, type, listener);
+ return this;
+ }
+
if (!this._events[type]) {
// Optimize the case of one listener. Don't need the extra array object.
this._events[type] = listener;
- } else if (isArray(this._events[type])) {
-
+ }
+ else if(typeof this._events[type] === 'function') {
+ // Adding the second element, need to change to array.
+ this._events[type] = [this._events[type], listener];
+ }
+ else if (isArray(this._events[type])) {
// If we've already got an array, just append.
this._events[type].push(listener);
// Check for listener leak
if (!this._events[type].warned) {
+
var m;
if (this._events.maxListeners !== undefined) {
m = this._events.maxListeners;
} else {
- m = defaultMaxListeners;
+ m = this.defaultMaxListeners;
}
if (m && m > 0 && this._events[type].length > m) {
+
this._events[type].warned = true;
console.error('(node) warning: possible EventEmitter memory ' +
'leak detected. %d listeners added. ' +
@@ -133,80 +307,141 @@ EventEmitter.prototype.addListener = function(type, listener) {
console.trace();
}
}
- } else {
- // Adding the second element, need to change to array.
- this._events[type] = [this._events[type], listener];
}
-
return this;
};
-EventEmitter.prototype.on = EventEmitter.prototype.addListener;
+EventEmitter.prototype.onAny = function(fn) {
-EventEmitter.prototype.once = function(type, listener) {
- if ('function' !== typeof listener) {
- throw new Error('.once only takes instances of Function');
+ if(!this._all) {
+ this._all = [];
}
- var self = this;
- function g() {
- self.removeListener(type, g);
- listener.apply(this, arguments);
- };
-
- g.listener = listener;
- self.on(type, g);
+ if (typeof fn !== 'function') {
+ throw new Error('onAny only accepts instances of Function');
+ }
- return this;
+ // Add the function to the event listener collection.
+ this._all.push(fn);
};
-EventEmitter.prototype.removeListener = function(type, listener) {
- if ('function' !== typeof listener) {
+EventEmitter.prototype.addListener = EventEmitter.prototype.on;
+
+EventEmitter.prototype.un = function(type, listener) {
+ if (typeof listener !== 'function') {
throw new Error('removeListener only takes instances of Function');
}
- // does not use listeners(), so no side effect of creating _events[type]
- if (!this._events || !this._events[type]) return this;
+ var handlers;
- var list = this._events[type];
+ if(this.wildcard) {
+ var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice();
+ var leaf = searchListenerTree.call(this, null, ns, this.listenerTree, 0);
+
+ if('undefined' === typeof leaf) { return this; }
+ handlers = leaf._listeners;
+ }
+ else {
+ // does not use listeners(), so no side effect of creating _events[type]
+ if (!this._events[type]) return this;
+ handlers = this._events[type];
+ }
+
+ if (isArray(handlers)) {
- if (isArray(list)) {
var position = -1;
- for (var i = 0, length = list.length; i < length; i++) {
- if (list[i] === listener ||
- (list[i].listener && list[i].listener === listener))
- {
+
+ for (var i = 0, length = handlers.length; i < length; i++) {
+ if (handlers[i] === listener ||
+ (handlers[i].listener && handlers[i].listener === listener) ||
+ (handlers[i]._origin && handlers[i]._origin === listener)) {
position = i;
break;
}
}
- if (position < 0) return this;
- list.splice(position, 1);
- if (list.length == 0)
+ if (position < 0) {
+ return this;
+ }
+
+ if(this.wildcard) {
+ leaf._listeners.splice(position, 1)
+ }
+ else {
+ this._events[type].splice(position, 1);
+ }
+
+ if (handlers.length === 0) {
+ if(this.wildcard) {
+ delete leaf._listeners;
+ }
+ else {
+ delete this._events[type];
+ }
+ }
+ }
+ else if (handlers === listener ||
+ (handlers.listener && handlers.listener === listener) ||
+ (handlers._origin && handlers._origin === listener)) {
+ if(this.wildcard) {
+ delete leaf._listeners;
+ }
+ else {
delete this._events[type];
- } else if (list === listener ||
- (list.listener && list.listener === listener))
- {
- delete this._events[type];
+ }
}
return this;
};
+EventEmitter.prototype.unAny = function(fn) {
+ var i = 0, l = 0, fns;
+ if (fn && this._all && this._all.length > 0) {
+ fns = this._all;
+ for(i = 0, l = fns.length; i < l; i++) {
+ if(fn === fns[i]) {
+ fns.splice(i, 1);
+ return this;
+ }
+ }
+ } else {
+ this._all = [];
+ }
+ return this;
+};
+
+EventEmitter.prototype.removeListener = EventEmitter.prototype.un;
+
EventEmitter.prototype.removeAllListeners = function(type) {
if (arguments.length === 0) {
- this._events = {};
+ !this._events || this.init();
return this;
}
- // does not use listeners(), so no side effect of creating _events[type]
- if (type && this._events && this._events[type]) this._events[type] = null;
+ if(this.wildcard) {
+ var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice();
+ var leaf = searchListenerTree.call(this, null, ns, this.listenerTree, 0);
+
+ if('undefined' === typeof leaf) { return this; }
+ leaf._listeners = null;
+ }
+ else {
+ if (!this._events[type]) return this;
+ this._events[type] = null;
+ }
return this;
};
EventEmitter.prototype.listeners = function(type) {
- if (!this._events) this._events = {};
+ 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;
+ }
+
+ this._events || this.init();
+
if (!this._events[type]) this._events[type] = [];
if (!isArray(this._events[type])) {
this._events[type] = [this._events[type]];
Something went wrong with that request. Please try again.