Skip to content

Instantly share code, notes, and snippets.

@getify
Created July 17, 2012 16:19
Show Gist options
  • Save getify/3130393 to your computer and use it in GitHub Desktop.
Save getify/3130393 to your computer and use it in GitHub Desktop.
extending the `EventEmitter` API with `either/or` pattern
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
}
}
@polotek
Copy link

polotek commented Jul 17, 2012

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment