-
-
Save getify/3130393 to your computer and use it in GitHub Desktop.
| EventEmitter.prototype.either = function(evt,cb) { | |
| var self = this, orAPI = {or: bindHandler}, handlers = {}; | |
| function bindHandler(evt,cb) { | |
| if (!handlers || handlers[evt]) return; | |
| handlers[evt] = function(){ | |
| for (var e in handlers) { | |
| self.removeListener(e,handlers[e]); | |
| } | |
| handlers = null; | |
| cb.apply(self,arguments); | |
| }; | |
| self.on(evt,handlers[evt]); | |
| return orAPI; | |
| } | |
| return bindHandler(evt,cb); | |
| }; |
| function Foo() { | |
| EventEmitter.call(this); | |
| } | |
| var foo = new Foo(); | |
| // ... | |
| foo | |
| .either("hello",function(){ alert("Hello"); }) | |
| .or("world",function(){ alert("World"); }) | |
| .or("yeah",function(){ alert("Yeah!"); }); | |
| /* Once any event in the set is fired, unbind all the listeners | |
| in this set. It's kind of like a `once()` that works across a | |
| set of listeners instead of just one. */ |
| // ok, so at the request of some in the community, here's a | |
| // more realistic usage for this pattern. | |
| // | |
| // Compare this version ("a") to the next one ("b"). This | |
| // version is probably the more traditional way such things | |
| // are done. Basically, you create a single event handler | |
| // that acts as a router by inspecting the data. | |
| var socket = io.connect(...); | |
| function login(u,p) { | |
| socket.once("login_status",function(data){ | |
| if (data.err) Error.handle(data); | |
| else UI.build(data); | |
| }); | |
| socket.emit("login",{u:u,p:p}); | |
| } | |
| UI = { | |
| build: function(data) { | |
| // build out the UI now that we're logged in | |
| } | |
| }; | |
| Error = { | |
| handle: function(data) { | |
| // display the error in a growl notice, for instance | |
| } | |
| } |
| // alternate way to do version "a" of the example, using | |
| // the `either/or` pattern. In this way, the event being | |
| // listened for effectively does the routing to different | |
| // handlers accordingly. | |
| var socket = io.connect(...); | |
| function login(u,p) { | |
| socket | |
| .either("login_successful", UI.build) | |
| .or("login_failed", Error.handle) | |
| socket.emit("login",{u:u,p:p}); | |
| } | |
| UI = { | |
| build: function(data) { | |
| // build out the UI now that we're logged in | |
| } | |
| }; | |
| Error = { | |
| handle: function(data) { | |
| // display the error in a growl notice, for instance | |
| } | |
| } |
I think I follow some of your reasoning here. But if things get to a certain level of hairy with multiple event types, then you might be better served with a full blown state machine. I mean even in this scenario, you're not handling states that you didn't plan for. And generally when you get a message you didn't expect, that should be treated as an error.
It might feel onerous to do non-trivial dispatching if you do it ad-hoc and piece meal everywhere. But if you have a protocol layer, on both the client and the server, that interprets all messages before they are surfaced to the app, then things become much more manageable. The details of that stuff is hidden. In short, for anything non-trivial, I don't recommend dealing with socket.io sockets directly.
I'm sure I'm still missing some of the nuance of your use cases. And I'm not opposed to this idea at all. I just don't think it fits with my personal esthetic. For socket.io, I usually just have an on('message') handler and interpret everything that comes over the wire.
Comparing
ex2-a.jsandex2-b.js, they seem very similar (they are). The difference is in the abstraction of theeither/orpattern to more complex scenarios where you have several events in the set that need to be routed, only one route of which will actually happen.In
ex2-a.js, the extra "ugliness" of creating a custom router doesn't look that bad, but it gets worse when there's lots of these event-sets in your code and you're defining this routing logic all over the place.Also, on the server side, "ex2-a.js" requires that you have potentially different sets of code, modules, etc, that handle each a different piece of logic ("login successful", "login failed"), they must all funnel their communication back through a single piece, so that the "login_status" event can be sent out, and the data that gets sent in that event is very different depending on the nature of which path was followed, etc.
It gets much uglier on the server as you try to negotiate shoving a bunch of different types of events into a single event name, so that client can then route them back out to different handlers.
Seems much cleaner to have a different event name for each logical path, and just fire out the appropriate event from the server-side module that's handling it, etc.