callback-hell
Version:
my abstraction for dealing with async functions that must run sequentially or can run in parallel
208 lines (176 loc) • 6.64 kB
JavaScript
var _ = require("underscore");
var u = require("util");
var s = require("string");
var WRITE_ORDER_FLAG = "__is_write_order__";
var WRAPPED_RESULT_FLAG = "__is_wrapped_result__";
// construct a filled wrapped result. Holds a meaningful value
function mkResult(result) {
return mkWrapped(null,result);
}
// construct a write order. This wraps a meaningful result with a key that describes where to write the value to
function mkWriteOrder(key,value) {
var w = { 'key' : key, 'value' : value };
w[WRITE_ORDER_FLAG] = true;
return w;
}
// construct a null wrapped result. Holds neither an error or a meaningful value
function mkNull() {
return mkWrapped(null,null);
}
// construct an error wrapped result. Holds an error
function mkError(error) {
return mkWrapped(error,null);
}
// constructs a wrapped result. These can contain a meaningful value or an error. If both are present, the error takes priority
function mkWrapped(error,result) {
var w = { "result" : result, "error" : error };
w[WRAPPED_RESULT_FLAG] = true;
return w;
}
// checks whether the wrapped result contains an error
function isError(wrappedResult) {
return ('error' in wrappedResult) && wrappedResult.error !== null && wrappedResult.error !== undefined;
}
// checks whether the wrapped result contains a meaningful value
function isResult(wrappedResult) {
return !isError(wrappedResult) && ('result' in wrappedResult ) && wrappedResult.result !== null && wrappedResult.result !== undefined;
}
function isWrapped(wrappedResult) {
return WRAPPED_RESULT_FLAG in wrappedResult;
}
// executes a write order by adding the result into a channel (object), using the key specified by the write order
function writeResult(writeOrder,channel) {
if(writeOrder[ WRITE_ORDER_FLAG ]=== true && writeOrder.key !== undefined && writeOrder.value !== undefined)
channel[writeOrder.key] = writeOrder.value;
}
// internal function for chaining asynchronous functions in sequence/serial
function callBackSerial( wrappedResult, calls, channel, finalCallBack) {
if (isError(wrappedResult)) {
finalCallBack(wrappedResult);
return;
}
if(isResult(wrappedResult))
writeResult(wrappedResult.result,channel);
if (calls.length === 0) {
finalCallBack(mkResult(channel));
return;
}
var currentCall = calls[0];
var remainingCalls = calls.slice(1);
currentCall(new mkReader(channel),function( wrapped ) {
callBackSerial( wrapped, remainingCalls, channel, finalCallBack);
});
}
// allows you to chain together multiple asynchronous functions in sequence/serial
function asyncSerial( calls, finalCallBack ) {
callBackSerial(mkNull(),calls,{}, finalCallBack);
}
// internal function for running asynchronous functions in parallel
function callBackParallel( wrappedResult, channel, finalCallBack, refCounter ) {
refCounter.count -=1;
if(isError(wrappedResult) && refCounter.count >= 0) {
refCounter.count = -1;
finalCallBack(wrappedResult);
return;
}
if(isResult(wrappedResult))
writeResult(wrappedResult.result,channel);
if(refCounter.count === 0) {
finalCallBack(mkResult(channel));
return;
}
}
// alows you to run independent asynchronous functions in parallel, only executing the final callback upon completion
function asyncParallel( calls, finalCallBack ) {
var channel = {};
var refCounter = { count : calls.length + 1 };
_.each(calls, function(c) {
c(function(wrapped) {
callBackParallel(wrapped,channel,finalCallBack,refCounter);
});
});
callBackParallel(mkNull(),channel,finalCallBack,refCounter);
}
// creates a reader object. This wraps around a channel and provides a way to read information written into the channel
function mkReader(sink) {
this._sink = sink;
this.get = function(key) {
return this._sink[key];
};
this.keys = function() {
return _.keys(this._sink);
};
this.contains = function(key) {
return (key in this._sink);
};
}
// wraps a callback such that it expects a single argument: (err|null)
function errorWrap(callBack) {
return function(err) { callBack(mkError(err)); };
}
// wraps a callback such that it expects two arguments: (err|null), (res|null)
function errorResultWrap(callBack) {
return function(err,res) { callBack( mkWrapped(err,res)); };
}
// wraps a callback such that it expects a single argument: res
function resultWrap(callBack) {
return function(res) { callBack( mkResult(res)); };
}
// wraps a callback such that any result it contains is mapped over by the 'mapper' function before being passed to the
// callback itsef
function mapWrap( cb, mapper ) {
return function(w) {
if(isResult(w))
w.result = mapper(w.result);
cb(w);
};
}
// wraps a callbacks such that any value is lifted into a write order with specified key before being passed to the inner callback
function writeWrap( cb, key ) {
return mapWrap(cb, function(x) { return mkWriteOrder( key, x ); });
}
// a common specialisation of 'mapWrap'. Performs the map: x -> x[index], where index is specified as the argument
function ixWrap(cb,ix) {
return mapWrap(cb, function(x) { return x[ix]; });
}
function asyncSerialArr(xs,fn,cb) {
var fns = _.map(xs, function(x,i) { return function(_r,cb) { fn(i,x,writeWrap(cb,i)); }; });
asyncSerial(fns,mapWrap( cb, function(r) {
return _.chain(r).pairs().sortBy( function(x) { return x[0]; }).map( function(x) { return x[1]; }).value();
}));
}
function asyncParallelArr(xs,fn,cb) {
var fns = _.map(xs, function(x,i) { return function(cb) { fn(i,x,writeWrap(cb,i)); }; });
asyncParallel(fns,mapWrap( cb, function(r) {
return _.chain(r).pairs().sortBy( function(x) { return x[0]; }).map( function(x) { return x[1]; }).value();
}));
}
function asyncParallelObj(xs,fn,cb) {
var fns = _.map(xs,function(v,k) { return function(cb) { fn(k,v,writeWrap(cb,k)); }; });
asyncParallel(fns,cb);
}
module.exports = {
// creating wrapped results
mkError : mkError,
mkResult : mkResult,
mkWrapped : mkWrapped,
mkNull : mkNull,
mkWriteOrder : mkWriteOrder,
//wrapped result checks
isWrapped : isWrapped,
isError : isError,
isResult : isResult,
//async chaining
asyncSerial : asyncSerial,
asyncSerialArr : asyncSerialArr,
asyncParallel : asyncParallel,
asyncParallelArr : asyncParallelArr,
asyncParallelObj : asyncParallelObj,
//callback wrapping fns
mw : mapWrap,
ww : writeWrap,
iw : ixWrap,
ew : errorWrap,
rw : resultWrap,
bw : errorResultWrap,
};