runnel
Version:
Simple and small flow control library to execute async functions in sequence
99 lines (84 loc) • 2.78 kB
JavaScript
;(function () {
'use strict';
var slice = Array.prototype.slice;
var isArray = typeof Array.isArray === 'function'
? Array.isArray
: function (a) { return typeof a === 'object' && !!a.length; };
function validate (funcs) {
if (funcs.length < 2)
throw new Error('Give runnel at least 2 functions to do any work.');
for (var i = 0; i < funcs.length; i++) {
if (typeof funcs[i] !== 'function')
throw new Error('All arguments passed to runnel need to be a function. Argument at (zero based) position ' + i + ' is not.');
}
}
function addDetails(err) {
try {
err.message = (err.message || '') + '\nDetails:\n' + err.stack;
} catch (e) {
// the error object is sealed, frozen or the message property is read-only
var newError = new Error();
newError.message = (err.message || '') + '\nDetails:\n' + err.stack;
newError.type = err.type;
newError.stack = err.stack;
err = newError;
}
return err;
}
function runnel (arg) {
var funcs = isArray(arg) ? arg : slice.call(arguments);
validate(funcs);
var done = funcs.pop()
, func = funcs.shift()
, bailed = false
;
function handler (err, res /* optional and could be more than one */) {
// Prevent re-triggering call chain when a func calls back with an err first and without one later
if (bailed) return;
var args;
// Bail if any of the funcs encounters a problem
if (err) {
bailed = true;
args = slice.call(arguments);
addDetails(err);
done.apply(this, args);
return;
}
func = funcs.shift();
if (func) {
// get args without err
args = slice.call(arguments, 1);
// this handler becomes the callback for the current func we are calling
args.push(handler);
try {
func.apply(this, args);
} catch (err) {
bailed = true;
done.call(this, addDetails(err));
return;
}
} else {
args = slice.call(arguments);
done.apply(this, args);
}
}
func.call(this, handler);
}
runnel.seed = function () {
var args = [null].concat(slice.call(arguments))
, that = this;
return function (cb) {
setTimeout(function () { cb.apply(that, args); }, 0);
};
};
if (typeof module === 'object' && typeof module.exports === 'object') {
// CommonJS, just export
module.exports = runnel;
} else if (typeof define === 'function' && define.amd) {
// AMD support
define(function () { return runnel; });
} else if (typeof window === 'object') {
// If no AMD and we are in the browser, attach to window
window.runnel = runnel;
}
})();