Skip to content

Instantly share code, notes, and snippets.

@JamieMason
Created July 5, 2011 11:59
Show Gist options
  • Save JamieMason/1064713 to your computer and use it in GitHub Desktop.
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.
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();
}
}
};
@JamieMason
Copy link
Author

A very crude example below which will log to the console;

_1) Create the group_

var group = new AsyncGroup(
    function (on)
    {
        console.log('Asynch 1 fires');
        setTimeout(on.success, Math.round(Math.random() * 5) * 1000);
    }
    , {
        success: function ()
        {
            console.log('Asynch 1 onSuccess', new Date());
            return 'Asynch 1 success return value';
        }
        , fail: function ()
        {
            console.log('Asynch 1 onFail', new Date());
            return 'Asynch 1 fail return value';
        }
    }
    , function (on)
    {
        console.log('Asynch 2 fires');
        setTimeout(on.fail, Math.round(Math.random() * 5) * 1000);
    }
    , {
        success: function ()
        {
            console.log('Asynch 2 onSuccess', new Date());
            return 'Asynch 2 success return value';
        }
        , fail: function ()
        {
            console.log('Asynch 2 onFail', new Date());
            return 'Asynch 2 fail return value';
        }
    }
);

_2) Call the group as often as you like, each call to the group will not clash with others_

group.run(function()
{
    console.log('1st call to group complete', arguments);
});

group.run(function()
{
    console.log('2nd call to group complete', arguments);
});

group.run(function()
{
    console.log('3rd call to group complete', arguments);
});

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