Created
July 5, 2011 11:59
-
-
Save JamieMason/1064713 to your computer and use it in GitHub Desktop.
Merge multiple asynchronous functions into a group, notifying you via callback when every request in the group has completed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function AsyncGroup () | |
{ | |
this.members = []; | |
this.callbacks = []; | |
AsyncGroup.statics.init.apply(this, arguments); | |
} | |
// statics used for utils to keep the instance API clear | |
AsyncGroup.statics = { | |
// when an on.complete, on.fail etc is called - store it's return value and record that one | |
// of the group members has responded. When all members have responded, call the group callback. | |
callbackRunner: function (fCallback, aResults, nGroupSize, onComplete) | |
{ | |
aResults.push(fCallback.apply(this, arguments)); | |
if (aResults.length === nGroupSize) | |
{ | |
onComplete(aResults); | |
} | |
} | |
, curry: function (fn /*, ... */) | |
{ | |
var curryArgs = Array.prototype.slice.call(arguments, 1); | |
return function ( /* ... */ ) | |
{ | |
var newArgs = Array.prototype.slice.call(arguments, 0), | |
mergedArgs = curryArgs.concat(newArgs); | |
return fn.apply(this, mergedArgs); | |
}; | |
} | |
// this could go in the constructor but I'm being fussy over adding a bit of memory to every instance | |
, init: function () | |
{ | |
var aArgs = Array.prototype.slice.call(arguments, 0); | |
if (aArgs.length % 2 !== 0) | |
{ | |
throw new Error('new AsyncGroup([fn:function, oCallbacks:Object, fn, oCallbacks...]) "Check supplied optional params follow this format"'); | |
} | |
do | |
{ | |
this.add.apply(this, aArgs.splice(0, 2)); | |
} | |
while (aArgs.length); | |
} | |
// this is curried in by AsyncGroup.run so you can call the group again before a previous call has completed. | |
// maybe the server hasn't responded yet - multiple calls to .run can be made this way without clashes. | |
, prepareCallbacks: function (fn, oCallbacks, aResults, nGroupSize, onComplete) | |
{ | |
var oDecoratedCallbacks = {} | |
, sKey; | |
for (sKey in oCallbacks) | |
{ | |
if (oCallbacks.hasOwnProperty(sKey)) | |
{ | |
oDecoratedCallbacks[sKey] = AsyncGroup.statics.curry(AsyncGroup.statics.callbackRunner, oCallbacks[sKey], aResults, nGroupSize, onComplete); | |
} | |
} | |
return oDecoratedCallbacks; | |
} | |
}; | |
AsyncGroup.prototype = { | |
// Add another async function with callbacks to the group | |
add: function (fn, oCallbacks) | |
{ | |
if (typeof fn !== 'function') | |
{ | |
throw new Error('AsyncGroup.add(fn:function, oCallbacks:Object) "fn supplied is not function"'); | |
} | |
if (oCallbacks.constructor !== Object) | |
{ | |
throw new Error('AsyncGroup.add(fn:function, oCallbacks:Object) "oCallbacks supplied is not object"'); | |
} | |
this.members.push(fn); | |
this.callbacks.push(oCallbacks); | |
} | |
// run every async function in the group and notify the callback after all of them have responded | |
, run: function (onComplete) | |
{ | |
if (typeof onComplete !== 'function') | |
{ | |
throw new Error('AsyncGroup.run(onComplete:function) "onComplete supplied is not function"'); | |
} | |
var aResults = [] | |
, i | |
, nGroupSize = this.members.length | |
, fn | |
, oCallbacks | |
, oDecoratedCallbacks; | |
for (i = 0; i < nGroupSize; i++) | |
{ | |
fn = this.members[i]; | |
oCallbacks = this.callbacks[i]; | |
oDecoratedCallbacks = AsyncGroup.statics.prepareCallbacks(fn, oCallbacks, aResults, nGroupSize, onComplete); | |
fn = AsyncGroup.statics.curry(fn, oDecoratedCallbacks); | |
fn(); | |
} | |
} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
A very crude example below which will log to the console;
_1) Create the group_
_2) Call the group as often as you like, each call to the group will not clash with others_