lib/async.js
function Series(asyncFncWrapper) {
    asyncFncWrapper = getAsyncFncWrapper(asyncFncWrapper);
    
    this.add = function() {       
        var fnc, args;
        if(arguments.length===1) {
            fnc=arguments[0];
        }
        else if(arguments.length > 1) {
            fnc=arguments[arguments.length - 1];
            args = [];
            for(var i=0;i<arguments.length-1;i++){
                args.push(arguments[i]);
            }
        }
        
        if(typeof fnc!=='function')
            throw new Error('Async Series: Last argument have to be function(arg1, arg2, ..., next).');
        
        this._fncQueue = this._fncQueue || [];
        this._fncQueue.push({ args:args, fnc:fnc });
        
        this.execute = function(finalFnc){
            var fncQueue = this._fncQueue;
            fncQueue.current = 0;
            fncQueue.scheduled = 0;
            
            function run(itm) {
                asyncFncWrapper(function(){
                    fncQueue.scheduled = fncQueue.current;
                    
                    if(itm.args) {
                        itm.args.push(next);
                        itm.fnc.apply(itm, itm.args);
                    }
                    else itm.fnc(next);
                });
            }
            
            function next(){
                if(!fncQueue || fncQueue.scheduled !== fncQueue.current)
                    throw new Error('Async Series: cannot execute next() more than once per function');
                
                // if(arguments.length > 0) {
                if(arguments.length > 0 && (arguments[0] !== null && typeof arguments[0] !== 'undefined')) {
                    var args = Array.prototype.slice.call(arguments);
                    fncQueue = null;
                    if(typeof finalFnc==='function')
                        asyncFncWrapper(function(){ finalFnc.apply(this, args); });
                }
                else if(fncQueue.current + 1 < fncQueue.length) {
                    fncQueue.current+=1;
                    run(fncQueue[fncQueue.current]);
                }
                else {
                    fncQueue = null;
                    if(typeof finalFnc==='function')
                        asyncFncWrapper(finalFnc);
                }
            }
            
            if(fncQueue && fncQueue.length>0)
                run(fncQueue[fncQueue.current]);
        };
        return this;
    };
    
    this.execute = function(fnc){ if(typeof fnc==='function') asyncFncWrapper(fnc); };
}
function Series(asyncFncWrapper) {
    asyncFncWrapper = getAsyncFncWrapper(asyncFncWrapper);
    
    this.add = function() {       
        var fnc, args;
        if(arguments.length===1) {
            fnc=arguments[0];
        }
        else if(arguments.length > 1) {
            fnc=arguments[arguments.length - 1];
            args = [];
            for(var i=0;i<arguments.length-1;i++){
                args.push(arguments[i]);
            }
        }
        
        if(typeof fnc!=='function')
            throw new Error('Async Series: Last argument have to be function(arg1, arg2, ..., next).');
        
        this._fncQueue = this._fncQueue || [];
        this._fncQueue.push({ args:args, fnc:fnc });
        
        this.execute = function(finalFnc){
            var fncQueue = this._fncQueue;
            fncQueue.current = 0;
            fncQueue.scheduled = 0;
            
            function run(itm) {
                asyncFncWrapper(function(){
                    fncQueue.scheduled = fncQueue.current;
                    
                    if(itm.args) {
                        itm.args.push(next);
                        itm.fnc.apply(itm, itm.args);
                    }
                    else itm.fnc(next);
                });
            }
            
            function next(){
                if(!fncQueue || fncQueue.scheduled !== fncQueue.current)
                    throw new Error('Async Series: cannot execute next() more than once per function');
                
                // if(arguments.length > 0) {
                if(arguments.length > 0 && (arguments[0] !== null && typeof arguments[0] !== 'undefined')) {
                    var args = Array.prototype.slice.call(arguments);
                    fncQueue = null;
                    if(typeof finalFnc==='function')
                        asyncFncWrapper(function(){ finalFnc.apply(this, args); });
                }
                else if(fncQueue.current + 1 < fncQueue.length) {
                    fncQueue.current+=1;
                    run(fncQueue[fncQueue.current]);
                }
                else {
                    fncQueue = null;
                    if(typeof finalFnc==='function')
                        asyncFncWrapper(finalFnc);
                }
            }
            
            if(fncQueue && fncQueue.length>0)
                run(fncQueue[fncQueue.current]);
        };
        return this;
    };
    
    this.execute = function(fnc){ if(typeof fnc==='function') asyncFncWrapper(fnc); };
}
async Series queue constructor
@param {Object} asyncFncWrapper (optional) by default is setImmediate, but can be setTimeout, or any custom function
@returns {Object}  series queue