Asynchronous chaining

9th January 2014

Javascript is an asynchronous language and expects methods to be called and perform asynchronous events. You can use functions as callbacks to be called when the methods asynchronous events have finished. If you need to chain these events, they become unmanagable. When dealing with raw Javascript it is nice to be able to just call your methods in an order you wish and then perform a single action when the asynchronous methods are complete.

The class below can be used to chain asynchronous functions together:

 function AsyncChain(args) {
if (args===undefined) args = {};

var defaults = {
callback: function() {},
synchronous: false,
persist: false
};

for (var key in defaults) {
if (!args.hasOwnProperty(key)) {
args[key] = defaults[key];
}
}

this._args = args;

this._async = [];
this._asyncs = {};
this._id = 0;
this._i = 0;
this._done = false;
}

AsyncChain.prototype.finished = function() {
return (this._i >= this._async.length);
};

AsyncChain.prototype.ready = function(callback) {
if (callback!==undefined) {
this._args.callback = callback;
}

//run the test once if it has not already been called
if (this.finished()) {
this._args.callback();
this.reset();
} else {
this.next();
}
};

AsyncChain.prototype.reset = function() {
this._i=0;
};

AsyncChain.prototype.next = function() {
if (!this.finished()) {
if (this._args.persist) {
var nextFuncKey = this._async[this._i];
this._i++;
} else {
var nextFuncKey = this._async.shift();
}

var nextFunc = this._asyncs[String(nextFuncKey)];

if (this._args.synchronous) {
nextFunc.func.apply(nextFunc.newThis, nextFunc.args);
this.next();
} else {
var self = this;
nextFunc.func.apply(nextFunc.newThis, nextFunc.args.concat([function() {
self.next();
}]));
}
} else {
this._args.callback();
this.reset();
}
};

AsyncChain.prototype.remove = function(key) {
var funcIndex = this._async.indexOf(key);

if (this._i > funcIndex) {
this._i--;
}

//if the index (i) has surpassed the index of the function in the list then decrement i
this._async.splice(funcIndex, 1);
delete this._asyncs[key];
};

AsyncChain.prototype.add = function(func, args, newThis) {
if (args === undefined) args = [];
if (newThis === undefined) newThis = this;

var newKey = String(this._id++);
this._asyncs[newKey] = {func: func, newThis: newThis, args: args};
this._async.push(newKey);

return newKey;
};

Usage

Here is an example of how the class should be used:

var startTime = new Date().getTime();
var bin = new AsyncChain();
bin.add(longTask, [1]);
bin.add(longTask2,[], bin);
bin.ready(done);

function done() { alert("Took " + (new Date().getTime() - startTime) + " ms"); };
function longTask(val, callback) { console.log(val); setTimeout(callback, 1000); };
function longTask2(callback) { console.log(this); setTimeout(callback, 3000); };

Make a comment

Contribute to this article and have your say.