UNPKG

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
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, };