Skip to content

Instantly share code, notes, and snippets.

@ahamid
Created November 8, 2011 07:17
Show Gist options
  • Save ahamid/1347206 to your computer and use it in GitHub Desktop.
Save ahamid/1347206 to your computer and use it in GitHub Desktop.
/**
* Allows for calling a method asynchronously and queueing pending callbacks to avoid
* race conditions.
*
* Example:
*
* function createUniverse(arg, callback) {
* // important stuff that should not run in parallel here
* callback(whatever);
* }
*
* You can now safely run this with queued callbacks:
*
* // or use bind/curry
* function inflationary(cb) { createUniverse("inflationary", cb); }
*
* multiplexCallback(inflationary, function() { ... thing one ... });
* multiplexCallback(inflationary, function() { ... thing two ... });
* multiplexCallback(inflationary, function() { ... thing three ... });
*/
function multiplexCallback(callbackable, callback) {
if (!callbackable.__multiplex_callback_list) {
callbackable.__multiplex_callback_list = [];
callbackable.__multiplex_callback_list.push(callback);
callbackable(function () {
for (var i = 0, l = callbackable.__multiplex_callback_list.length; i < l; i++) {
callbackable.__multiplex_callback_list[i]();
}
// delete processed callbacks
delete callbackable.__multiplex_callback_list;
});
} else {
callbackable.__multiplex_callback_list.push(callback);
}
}
function makeMultiplexedCallbackable(callbackable) {
return function(callback) {
return multiplexCallback(callbackable, callback);
}
}
describe("#multiplexedCallback", function() {
it("it should invoke all callbacks after asynchronous invocation", function() {
var times_called = {
red: 0,
green: 0
};
function paint_bikeshed(color, callback) {
// pretend important async stuff is here
times_called[color] = times_called[color] + 1;
setTimeout(callback, 1000);
}
var red_one = "";
var red_two = "";
var red_three = "";
var red_done = false;
var red_callbackable = makeMultiplexedCallbackable(function(cb) { paint_bikeshed("red", cb); });
var green_one = "";
var green_two = "";
var green_three = "";
var green_done = false;
var green_callbackable = makeMultiplexedCallbackable(function(cb) { paint_bikeshed("green", cb); });
runs(function() {
red_callbackable(function() { red_one = "red: sanding"; });
red_callbackable(function() { red_two = "red: priming"; });
red_callbackable(function() { red_three = "red: painting"; red_done = true; });
green_callbackable(function() { green_one = "green: sanding"; });
green_callbackable(function() { green_two = "green: priming"; });
green_callbackable(function() { green_three = "green: painting"; green_done = true; });
});
this.waitsFor(function() {
return red_done && green_done;
}, 20000);
runs(function() {
// method only called once, but all callbacks are called afterwards!
expect(times_called.red).toEqual(1);
expect(red_one).toEqual("red: sanding");
expect(red_two).toEqual("red: priming");
expect(red_three).toEqual("red: painting");
// method only called once, but all callbacks are called afterwards!
expect(times_called.green).toEqual(1);
expect(green_one).toEqual("green: sanding");
expect(green_two).toEqual("green: priming");
expect(green_three).toEqual("green: painting");
// the callback cache should be reset, so we should be able to execute it again
times_called.red = 0;
times_called.green = 0;
red_done = false;
green_done = false;
red_callbackable(function() { red_one = "red: something else entirely"; red_done = true; });
green_callbackable(function() { green_one = "green: something else entirely"; green_done = true; });
});
this.waitsFor(function() {
return red_done && green_done;
}, 20000);
runs(function() {
expect(times_called.red).toEqual(1);
expect(red_one).toEqual("red: something else entirely");
expect(times_called.green).toEqual(1);
expect(green_one).toEqual("green: something else entirely");
});
});
});
@ahamid
Copy link
Author

ahamid commented Nov 8, 2011

The necessity for wrapping the target function is ugly and could be eased with some currying.
I had considered supporting wrapping/currying the function internally, but multiplexCallback can not know the signature of the function and therefore can't know how to pass its own internal callback, so this is left up to the user. A special case convention of "the last parameter is always the callback" would allow passing unwrapped function with arguments:

mutiplexCallback(tgt_function, arg1, arg2, key, callback)

Passing a key for the method is also clunky. The invocation needs to be identified somehow, and this can only come from the user (could depend on function args). For comparison see Underscore's memoize (Underscore uses the first parameter as the default key).

@ahamid
Copy link
Author

ahamid commented Nov 8, 2011

Oh, yeah, the spec is not canonically clean unlinted javascript and I don't really care

@ahamid
Copy link
Author

ahamid commented Nov 8, 2011

is there a library that already has utilities like this?

@ahamid
Copy link
Author

ahamid commented Nov 9, 2011

http://augmentjs.com has bind
http://supplementjs.com, http://kitcambridge.github.com/maddy, http://functools.kodfabrik.com/man.html have curry/partial application
https://github.com/kriskowal/q - support for sophisticated asynchronous processing; probably can do the above with "promises"

@ahamid
Copy link
Author

ahamid commented Nov 9, 2011

Moved to jsFiddle with new approach by @mkuklis http://jsfiddle.net/JNFQy/1/

@ahamid
Copy link
Author

ahamid commented Nov 9, 2011

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