latte_lib
Version:
1,244 lines (1,161 loc) • 38.3 kB
JavaScript
var latte_lib = require("./lib.js");
if(!latte_lib) {
console.log("no load lib");
}
/**
* @class async
* @namespace latte_lib
* @module basic
*/
(function() {
var _self = this;
this.setImmediate = latte_lib.setImmediate;
/**
* 单次执行
* @method only_once
* @static
* @param {function} fn 只执行一次的函数
* @public
* @async
* @since 0.0.1
* @example
var async = require("latte_lib").async;
var f = async.only_once(function(data) {
log(data);
});
f("a");
f("b");
//"a"
//error callback was already called.
*/
var only_once = this.only_once = function(fn) {
var called = false;
return function() {
if (called) throw new Error("Callback was already called.");
called = true;
fn.apply(_self, arguments);
}
};
/**
* 并行执行
* @method forEach
* @static
* @param arr {array} 需要被执行函数的数组
* @param iterator {function} 执行函数
* @param callback {function} 回调函数
* @async
* @since 0.0.1
* @example
var async = require("latte_lib").async;
var result = [];
async.forEach([1,2,3,4], function(data, callback) {
if(data == 3) {
callback("is 3");
}else{
result.push(data * 2);
callback();
}
}, function(err, data) {
log(err); //is 3
log(result);// [2,4]
});
var result2 = [];
async.forEach([1,2,3,4], function(data, callback) {
if(data == 3) {
setTimeout(function() {
callback("is 3");
}, 1);
}else{
result2.push(data * 2);
callback();
}
}, function(err, data) {
log(err); //is 3
log(result2);// [2,4,8]
});
*
*/
this.forEach = this.each = function(arr, iterator, callback) {
callback = callback || function(){};
if(!arr.length) {
return callback();
}
var completed = 0;
latte_lib.forEach(arr, function (x) {
iterator(x, only_once(done) );
});
function done(err) {
if (err) {
callback(err);
callback = function () {};
}
else {
completed += 1;
if (completed >= arr.length) {
callback();
}
}
}
};
/**
串行执行
@method forEachSeries
@static
@param arr {array} 需要被执行函数的数组
@param iterator {function} 执行函数
@param callback {function} 回调函数
@sync
@since 0.0.1
@example
var async = require("latte_lib").async;
var result = [];
async.forEachSeries([1,2,3,4], function(data, callback) {
if(data == 3) {
callback("is 3");
}else{
result.push(data * 2);
callback();
}
}, function(err, data) {
log(err); //is 3
log(result);// [2,4]
});
var result2 = [];
async.forEachSeries([1,2,3,4], function(data, callback) {
if(data == 3) {
setTimeout(function() {
callback("is 3");
}, 1);
}else{
result2.push(data * 2);
callback();
}
}, function(err, data) {
log(err); //is 3
log(result2);// [2,4,8]
});
*/
this.forEachSeries = this.eachSeries = function(arr, iterator, callback) {
callback = callback || function() {};
if (!arr.length) {
return callback();
}
var completed = 0;
(function iterate() {
iterator(arr[completed], function (err) {
if (err) {
callback(err);
callback = function () {};
}
else {
completed += 1;
if (completed >= arr.length) {
callback();
}
else {
iterate();
}
}
});
})();
};
this.forEachLimit = this.eachLimit = function(arr, limit, iterator, callback) {
var fn = _eachLimit(limit);
fn.apply(null, [arr, iterator, callback]);
};
var _eachLimit = function(limit) {
return function(arr, iterator, callback) {
callback = callback || function() {};
if (!arr.length || limit <= 0) {
return callback();
}
var completed = 0;
var started = 0;
var running = 0;
(function replenish () {
if (completed >= arr.length) {
return callback();
}
while (running < limit && started < arr.length) {
started += 1;
running += 1;
iterator(arr[started - 1], function (err) {
if (err) {
callback(err);
callback = function () {};
}
else {
completed += 1;
running -= 1;
if (completed >= arr.length) {
callback();
}
else {
replenish();
}
}
});
}
})();
};
};
var doParallel = function (fn) {
return function () {
var args = Array.prototype.slice.call(arguments);
return fn.apply(null, [_self.each].concat(args));
};
};
var doParallelLimit = function(limit, fn) {
return function () {
var args = Array.prototype.slice.call(arguments);
return fn.apply(null, [_eachLimit(limit)].concat(args));
};
};
var doSeries = function (fn) {
return function () {
var args = Array.prototype.slice.call(arguments);
return fn.apply(null, [_self.eachSeries].concat(args));
};
};
var _asyncMap = function(eachfn, arr, iterator, callback) {
arr = latte_lib.map(arr, function(x, i) {
return {
index: i,
value: x
};
});
if (!callback) {
eachfn(arr, function (x, callback) {
iterator(x.value, function (err) {
callback(err);
});
});
} else {
var results = [];
eachfn(arr, function (x, callback) {
iterator(x.value, function (err, v) {
results[x.index] = v;
callback(err);
});
}, function (err) {
callback(err, results);
});
}
};
this.map = doParallel(_asyncMap);
this.mapSeries = doSeries(_asyncMap);
var _mapLimit = function(limit) {
return doParallelLimit(limit, _asyncMap);
};
this.mapLimit = function(arr, limit, iterator, callback) {
return _mapLimit(limit)(arr, iterator, callback);
};
this.inject = this.foldl = this.reduce = function(arr, memo, iterator, callback) {
_self.eachSeries(arr, function(x, callback) {
iterator(memo, x, function (err, v) {
memo = v;
callback(err);
});
}, function (err) {
callback(err, memo);
});
};
this.foldr = this.reduceRight = function (arr, memo, iterator, callback) {
var reversed = latte_lib.map(arr, function (x) {
return x;
}).reverse();
_self.reduce(reversed, memo, iterator, callback);
};
var _filter = function (eachfn, arr, iterator, callback) {
var results = [];
arr = latte_lib.map(arr, function (x, i) {
return {index: i, value: x};
});
eachfn(arr, function (x, callback) {
iterator(x.value, function (v) {
if (v) {
results.push(x);
}
callback();
});
}, function (err) {
callback(latte_lib.map(results.sort(function (a, b) {
return a.index - b.index;
}), function (x) {
return x.value;
}));
});
};
this.select = this.filter = doParallel(_filter);
this.selectSeries = this.filterSeries = doSeries(_filter);
var _reject = function (eachfn, arr, iterator, callback) {
var results = [];
arr = latte_lib.map(arr, function (x, i) {
return {index: i, value: x};
});
eachfn(arr, function (x, callback) {
iterator(x.value, function (v) {
if (!v) {
results.push(x);
}
callback();
});
}, function (err) {
callback(latte_lib.map(results.sort(function (a, b) {
return a.index - b.index;
}), function (x) {
return x.value;
}));
});
};
this.reject = doParallel(_reject);
this.rejectSeries = doSeries(_reject);
var _detect = function (eachfn, arr, iterator, main_callback) {
eachfn(arr, function (x, callback) {
iterator(x, function (result) {
if (result) {
main_callback(x);
main_callback = function () {};
}
else {
callback();
}
});
}, function (err) {
main_callback();
});
};
this.detect = doParallel(_detect);
this.detectSeries = doSeries(_detect);
this.any = this.some = function(arr, iterator, main_callback) {
_self.each(arr, function (x, callback) {
iterator(x, function (v) {
if (v) {
main_callback(true);
main_callback = function () {};
}
callback();
});
}, function (err) {
main_callback(false);
});
};
this.all = this.every = function (arr, iterator, main_callback) {
_self.each(arr, function (x, callback) {
iterator(x, function (v) {
if (!v) {
main_callback(false);
main_callback = function () {};
}
callback();
});
}, function (err) {
main_callback(true);
});
};
this.sortBy = function (arr, iterator, callback) {
_self.map(arr, function (x, callback) {
iterator(x, function (err, criteria) {
if (err) {
callback(err);
}
else {
callback(null, {value: x, criteria: criteria});
}
});
}, function (err, results) {
if (err) {
return callback(err);
}
else {
var fn = function (left, right) {
var a = left.criteria, b = right.criteria;
return a < b ? -1 : a > b ? 1 : 0;
};
callback(null, latte_lib.map(results.sort(fn), function (x) {
return x.value;
}));
}
});
};
/**
自动 并行 如果有依赖的话等依赖好了在执行
@method auto
@static
@param {json} tasks
@param {function} callback
@async
@since 0.0.1
@example
var async = require("latte_lib").async;
async.auto({
a: ["c",function(callback) {
log("a");
callback(null,3);
}],
b: function(callback) {
log("b");
callback(null, 1);
},
c: function(callback) {
log("c");
callback(null, 2);
},
d: ["a", function(callback) {
log("d");
callback(null, 4);
}]
}, function(err, results) {
log("err:",err);
log("results:", results);// {"b":1,"c":2,"a":3,"d":4}
});
async.auto({
a: ["c",function(callback) {
log("a");
callback("is 3", 3);
}],
b: function(callback) {
log("b");
callback(null, 1);
},
c: function(callback) {
log("c");
callback(null, 2);
},
d: ["a", function(callback) {
log("d");
callback(null, 4);
}]
}, function(err, results) {
log("err:",err); // is 3
log("results:", results);// {"b":1,"c":2, "a":3}
});
*/
this.auto = function (tasks, callback) {
callback = callback || function () {};
var keys = latte_lib.keys(tasks);
var remainingTasks = keys.length
if (!remainingTasks) {
return callback();
}
var results = {};
var listeners = [];
var addListener = function (fn) {
listeners.unshift(fn);
};
var removeListener = function (fn) {
for (var i = 0; i < listeners.length; i += 1) {
if (listeners[i] === fn) {
listeners.splice(i, 1);
return;
}
}
};
var taskComplete = function () {
remainingTasks--
latte_lib.forEach(listeners.slice(0), function (fn) {
fn();
});
};
addListener(function () {
if (!remainingTasks) {
var theCallback = callback;
// prevent final callback from calling itself if it errors
callback = function () {};
theCallback(null, results);
}
});
latte_lib.forEach(keys, function (k) {
var task = latte_lib.isArray(tasks[k]) ? tasks[k]: [tasks[k]];
var taskCallback = function (err) {
var args = Array.prototype.slice.call(arguments, 1);
if (args.length <= 1) {
args = args[0];
}
if (err) {
var safeResults = {};
latte_lib.forEach(latte_lib.keys(results), function(rkey) {
safeResults[rkey] = results[rkey];
});
safeResults[k] = args;
callback(err, safeResults);
// stop subsequent errors hitting callback multiple times
callback = function () {};
}
else {
results[k] = args;
latte_lib.setImmediate(taskComplete);
}
};
var requires = task.slice(0, Math.abs(task.length - 1)) || [];
var ready = function () {
return latte_lib.reduce(requires, function (a, x) {
return (a && results.hasOwnProperty(x));
}, true) && !results.hasOwnProperty(k);
};
if (ready()) {
task[task.length - 1](taskCallback, results);
}
else {
var listener = function () {
if (ready()) {
removeListener(listener);
task[task.length - 1](taskCallback, results);
}
};
addListener(listener);
}
});
};
this.retry = function(times, task, callback) {
var DEFAULT_TIMES = 5;
var attempts = [];
// Use defaults if times not passed
if (typeof times === 'function') {
callback = task;
task = times;
times = DEFAULT_TIMES;
}
// Make sure times is a number
times = parseInt(times, 10) || DEFAULT_TIMES;
var wrappedTask = function(wrappedCallback, wrappedResults) {
var retryAttempt = function(task, finalAttempt) {
return function(seriesCallback) {
task(function(err, result){
seriesCallback(!err || finalAttempt, {err: err, result: result});
}, wrappedResults);
};
};
while (times) {
attempts.push(retryAttempt(task, !(times-=1)));
}
_self.series(attempts, function(done, data){
data = data[data.length - 1];
(wrappedCallback || callback)(data.err, data.result);
});
}
// If a callback is passed, run this as a controll flow
return callback ? wrappedTask() : wrappedTask
};
this.waterfall = function (tasks, callback) {
callback = callback || function () {};
if (!latte_lib.isArray(tasks)) {
var err = new Error('First argument to waterfall must be an array of functions');
return callback(err);
}
if (!tasks.length) {
return callback();
}
var wrapIterator = function (iterator) {
return function (err) {
if (err) {
callback.apply(null, arguments);
callback = function () {};
}
else {
var args = Array.prototype.slice.call(arguments, 1);
var next = iterator.next();
if (next) {
args.push(wrapIterator(next));
}
else {
args.push(callback);
}
latte_lib.setImmediate(function () {
iterator.apply(null, args);
});
}
};
};
wrapIterator(_self.iterator(tasks))();
};
var _parallel = function(eachfn, tasks, callback) {
callback = callback || function () {};
if (latte_lib.isArray(tasks)) {
eachfn.map(tasks, function (fn, callback) {
if (fn) {
fn(function (err) {
var args = Array.prototype.slice.call(arguments, 1);
if (args.length <= 1) {
args = args[0];
}
callback.call(null, err, args);
});
}
}, callback);
}
else {
var results = {};
eachfn.each(latte_lib.keys(tasks), function (k, callback) {
tasks[k](function (err) {
var args = Array.prototype.slice.call(arguments, 1);
if (args.length <= 1) {
args = args[0];
}
results[k] = args;
callback(err);
});
}, function (err) {
callback(err, results);
});
}
};
/**
并行
@method parallel
@async
@param {function[]} tasks
@param {function} callback
@example
var async = require("latte_lib").async;
async.parallel([
function(cb) {
cb(null, 1);
},
function(cb) {
setTimeout(function() {
cb("is 2");
}, 1);
},
function(cb) {
cb(null, 3);
}
],function(err, result) {
log(err); //is 2
log(result);//[1,null,3]
});
*/
this.parallel = function (tasks, callback) {
_parallel({ map: _self.map, each: _self.each }, tasks, callback);
};
this.parallelLimit = function(tasks, limit, callback) {
_parallel({ map: _mapLimit(limit), each: _eachLimit(limit) }, tasks, callback);
};
/**
@method series
@async
@param {function[]} tasks
@param {function} callback
@example
var async = require("latte_lib").async;
async.series([
function(cb) {
cb(null, 1);
},
function(cb) {
setTimeout(function() {
cb("is 2");
}, 1);
},
function(cb) {
cb(null, 3);
}
],function(err, result) {
log(err); //is 2
log(result);//[1,null]
});
*/
this.series = function (tasks, callback) {
callback = callback || function () {};
if (latte_lib.isArray(tasks)) {
_self.mapSeries(tasks, function (fn, callback) {
if (fn) {
fn(function (err) {
var args = Array.prototype.slice.call(arguments, 1);
if (args.length <= 1) {
args = args[0];
}
callback.call(null, err, args);
});
}
}, callback);
}
else {
var results = {};
_self.eachSeries(_keys(tasks), function (k, callback) {
tasks[k](function (err) {
var args = Array.prototype.slice.call(arguments, 1);
if (args.length <= 1) {
args = args[0];
}
results[k] = args;
callback(err);
});
}, function (err) {
callback(err, results);
});
}
};
this.iterator = function (tasks) {
var makeCallback = function (index) {
var fn = function () {
if (tasks.length) {
tasks[index].apply(null, arguments);
}
return fn.next();
};
fn.next = function () {
return (index < tasks.length - 1) ? makeCallback(index + 1): null;
};
return fn;
};
return makeCallback(0);
};
this.apply = function (fn) {
var args = Array.prototype.slice.call(arguments, 1);
return function () {
return fn.apply(
null, args.concat(Array.prototype.slice.call(arguments))
);
};
};
var _concat = function (eachfn, arr, fn, callback) {
var r = [];
eachfn(arr, function (x, cb) {
fn(x, function (err, y) {
r = r.concat(y || []);
cb(err);
});
}, function (err) {
callback(err, r);
});
};
this.concat = doParallel(_concat);
this.concatSeries = doSeries(_concat);
this.whilst = function (test, iterator, callback) {
if (test()) {
iterator(function (err) {
if (err) {
return callback(err);
}
_self.whilst(test, iterator, callback);
});
}
else {
callback();
}
};
this.doWhilst = function (iterator, test, callback) {
iterator(function (err) {
if (err) {
return callback(err);
}
var args = Array.prototype.slice.call(arguments, 1);
if (test.apply(null, args)) {
_self.doWhilst(iterator, test, callback);
}
else {
callback();
}
});
};
this.until = function(test, iterator, callback) {
if (!test()) {
iterator(function (err) {
if (err) {
return callback(err);
}
_self.until(test, iterator, callback);
});
}
else {
callback();
}
};
this.doUntil = function (iterator, test, callback) {
iterator(function (err) {
if (err) {
return callback(err);
}
var args = Array.prototype.slice.call(arguments, 1);
if (!test.apply(null, args)) {
_self.doUntil(iterator, test, callback);
}
else {
callback();
}
});
};
this.queue = function (worker, concurrency) {
if (concurrency === undefined) {
concurrency = 1;
}
function _insert(q, data, pos, callback) {
if (!q.started){
q.started = true;
}
if (!_isArray(data)) {
data = [data];
}
if(data.length == 0) {
// call drain immediately if there are no tasks
return latte_lib.setImmediate(function() {
if (q.drain) {
q.drain();
}
});
}
latte_lib.forEach(data, function(task) {
var item = {
data: task,
callback: typeof callback === 'function' ? callback : null
};
if (pos) {
q.tasks.unshift(item);
} else {
q.tasks.push(item);
}
if (q.saturated && q.tasks.length === q.concurrency) {
q.saturated();
}
latte_lib.setImmediate(q.process);
});
}
var workers = 0;
var q = {
tasks: [],
concurrency: concurrency,
saturated: null,
empty: null,
drain: null,
started: false,
paused: false,
push: function (data, callback) {
_insert(q, data, false, callback);
},
kill: function () {
q.drain = null;
q.tasks = [];
},
unshift: function (data, callback) {
_insert(q, data, true, callback);
},
process: function () {
if (!q.paused && workers < q.concurrency && q.tasks.length) {
var task = q.tasks.shift();
if (q.empty && q.tasks.length === 0) {
q.empty();
}
workers += 1;
var next = function () {
workers -= 1;
if (task.callback) {
task.callback.apply(task, arguments);
}
if (q.drain && q.tasks.length + workers === 0) {
q.drain();
}
q.process();
};
var cb = only_once(next);
worker(task.data, cb);
}
},
length: function () {
return q.tasks.length;
},
running: function () {
return workers;
},
idle: function() {
return q.tasks.length + workers === 0;
},
pause: function () {
if (q.paused === true) { return; }
q.paused = true;
q.process();
},
resume: function () {
if (q.paused === false) { return; }
q.paused = false;
q.process();
}
};
return q;
};
this.priorityQueue = function(worker, concurrency) {
function _compareTasks(a, b){
return a.priority - b.priority;
};
function _binarySearch(sequence, item, compare) {
var beg = -1,
end = sequence.length - 1;
while (beg < end) {
var mid = beg + ((end - beg + 1) >>> 1);
if (compare(item, sequence[mid]) >= 0) {
beg = mid;
} else {
end = mid - 1;
}
}
return beg;
}
function _insert(q, data, priority, callback) {
if (!q.started){
q.started = true;
}
if (!_isArray(data)) {
data = [data];
}
if(data.length == 0) {
// call drain immediately if there are no tasks
return latte_lib.setImmediate(function() {
if (q.drain) {
q.drain();
}
});
}
latte_lib.forEach(data, function(task) {
var item = {
data: task,
priority: priority,
callback: typeof callback === 'function' ? callback : null
};
q.tasks.splice(_binarySearch(q.tasks, item, _compareTasks) + 1, 0, item);
if (q.saturated && q.tasks.length === q.concurrency) {
q.saturated();
}
latte_lib.setImmediate(q.process);
});
}
// Start with a normal queue
var q = _self.queue(worker, concurrency);
// Override push to accept second parameter representing priority
q.push = function (data, priority, callback) {
_insert(q, data, priority, callback);
};
// Remove unshift function
delete q.unshift;
return q;
};
this.cargo = function (worker, payload) {
var working = false,
tasks = [];
var cargo = {
tasks: tasks,
payload: payload,
saturated: null,
empty: null,
drain: null,
drained: true,
push: function (data, callback) {
if (!latte_lib.isArray(data)) {
data = [data];
}
latte_lib.forEach(data, function(task) {
tasks.push({
data: task,
callback: typeof callback === 'function' ? callback : null
});
cargo.drained = false;
if (cargo.saturated && tasks.length === payload) {
cargo.saturated();
}
});
latte_lib.setImmediate(cargo.process);
},
process: function process() {
if (working) return;
if (tasks.length === 0) {
if(cargo.drain && !cargo.drained) cargo.drain();
cargo.drained = true;
return;
}
var ts = typeof payload === 'number'
? tasks.splice(0, payload)
: tasks.splice(0, tasks.length);
var ds = latte_lib.map(ts, function (task) {
return task.data;
});
if(cargo.empty) cargo.empty();
working = true;
worker(ds, function () {
working = false;
var args = arguments;
latte_lib.forEach(ts, function (data) {
if (data.callback) {
data.callback.apply(null, args);
}
});
process();
});
},
length: function () {
return tasks.length;
},
running: function () {
return working;
}
};
return cargo;
};
var _console_fn = function (name) {
return function (fn) {
var args = Array.prototype.slice.call(arguments, 1);
fn.apply(null, args.concat([function (err) {
var args = Array.prototype.slice.call(arguments, 1);
if (typeof console !== 'undefined') {
if (err) {
if (console.error) {
console.error(err);
}
}
else if (console[name]) {
latte_lib.forEach(args, function (x) {
console[name](x);
});
}
}
}]));
};
};
this.log = _console_fn('log');
this.dir = _console_fn('dir');
this.memoize = function (fn, hasher) {
var memo = {};
var queues = {};
hasher = hasher || function (x) {
return x;
};
var memoized = function () {
var args = Array.prototype.slice.call(arguments);
var callback = args.pop();
var key = hasher.apply(null, args);
if (key in memo) {
latte_lib.nextTick(function () {
callback.apply(null, memo[key]);
});
}
else if (key in queues) {
queues[key].push(callback);
}
else {
queues[key] = [callback];
fn.apply(null, args.concat([function () {
memo[key] = arguments;
var q = queues[key];
delete queues[key];
for (var i = 0, l = q.length; i < l; i++) {
q[i].apply(null, arguments);
}
}]));
}
};
memoized.memo = memo;
memoized.unmemoized = fn;
return memoized;
};
this.unmemoize = function (fn) {
return function () {
return (fn.unmemoized || fn).apply(null, arguments);
};
};
this.times = function (count, iterator, callback) {
var counter = [];
for (var i = 0; i < count; i++) {
counter.push(i);
}
return _self.map(counter, iterator, callback);
};
this.timesSeries = function (count, iterator, callback) {
var counter = [];
for (var i = 0; i < count; i++) {
counter.push(i);
}
return _self.mapSeries(counter, iterator, callback);
};
/**
@method seq
@static
@async
@param {function[]} functions
@return {function}
@since 0.0.1
@example
var async = require("latte_lib").async;
var fun = async.seq(function(a, callback) {
log("1",a);//2
callback(null, a+1, a-1);
}, function(data1, data2, callback) {
log("2",data1,data2);//3,1
callback("is 2", (data1 + data2 + 2) / (data1- data2 + 2) );
});
fun(2, function(err,b,c) {
log(err ,b,c);//is 2, 1.5
});
*/
this.seq = function (/* functions... */) {
var fns = arguments;
return function () {
var that = this;
var args = Array.prototype.slice.call(arguments);
var callback = args.pop();
_self.reduce(fns, args, function (newargs, fn, cb) {
fn.apply(that, newargs.concat([function () {
var err = arguments[0];
var nextargs = Array.prototype.slice.call(arguments, 1);
cb(err, nextargs);
}]))
},
function (err, results) {
callback.apply(that, [err].concat(results));
});
};
};
this.compose = function (/* functions... */) {
//颠倒参数
return _self.seq.apply(null, Array.prototype.reverse.call(arguments));
};
var _applyEach = function (eachfn, fns /*args...*/) {
var go = function () {
var that = this;
var args = Array.prototype.slice.call(arguments);
var callback = args.pop();
return eachfn(fns, function (fn, cb) {
fn.apply(that, args.concat([cb]));
},
callback);
};
if (arguments.length > 2) {
var args = Array.prototype.slice.call(arguments, 2);
return go.apply(this, args);
}
else {
return go;
}
};
this.applyEach = doParallel(_applyEach);
this.applyEachSeries = doSeries(_applyEach);
/**
循环执行出现错误停止
@method forever
@static
@param fn {function} 循环执行到函数
@param callback {function} 循环执行出现错误之后回调函数
@example
var async = require("latte_lib").async;
var i = 0;
async.forever(function(next) {
if(++i == 3) {
next("is 3");
}else{
log("forever", i);
next();
};
}, function(err) {
log(err);
});
*/
this.forever = function (fn, callback) {
function next(err) {
if (err) {
if (callback) {
return callback(err);
}
throw err;
}
fn(next);
}
next();
};
}).call(module.exports);