howdo
Version:
472 lines (383 loc) • 11.7 kB
JavaScript
/**
* Howdo
* 适应了 global 与 window
* @author ydr.me
* @create 2014年7月26日19:28:27
* @update 2014年8月26日13:09:31
* @update 2014年10月24日00:24:32
* @update 2015年02月04日11:42:57
* @update 2015年07月01日17:35:20
* @update 2015年11月27日10:57:12@3.0.0
*/
;
var _global = typeof window !== 'undefined' ? window : global;
var slice = Array.prototype.slice;
var noop = function () {
// ignore
};
/**
* 判断是否为函数
* @param obj
* @returns {boolean}
*/
var isFunction = function (obj) {
return typeof obj === 'function';
};
/**
* 遍历
* @param object
* @param callback
*/
var each = function (object, callback) {
var i;
var j;
if (object && object.constructor === Array) {
for (i = 0, j = object.length; i < j; i++) {
if (callback(i, object[i]) === false) {
break;
}
}
} else if (typeof object === "object") {
for (i in object) {
if (object.hasOwnProperty && object.hasOwnProperty(i)) {
if (callback(i, object[i]) === false) {
break;
}
} else {
if (callback(i, object[i]) === false) {
break;
}
}
}
}
};
/**
* 下一次
* @param callback
*/
var nextTick = function (callback) {
setTimeout(callback, 0);
};
module.exports = {
task: function () {
if (this.constructor === Howdo) {
return this;
}
var args = slice.call(arguments);
var howdo = new Howdo();
return howdo.task.apply(howdo, args);
},
each: function () {
if (this.constructor === Howdo) {
return this;
}
var args = slice.call(arguments);
var howdo = new Howdo();
return howdo.each.apply(howdo, args);
}
};
//////////////////////////////////////////////////////////////////////
/////////////////////////[ constructor ]//////////////////////////////
//////////////////////////////////////////////////////////////////////
// 构造函数
function Howdo() {
var the = this;
the._executed = false;
the._ignoreErr = false;
the._taskIndex = -1;
the._taskList = [];
the._tryCallbackList = [];
the._catchCallbackList = [];
the._contextList = [];
the._allCallback = null;
}
Howdo.prototype = {
constructor: Howdo,
/**
* 单次分配任务
* @param {Function} fn 任务函数
* @return Howdo
* @chainable
*/
task: function (fn) {
var the = this;
if (!isFunction(fn)) {
throw new TypeError('howdo `task` must be a function');
}
the._taskIndex++;
the._taskList.push(fn);
the._contextList.push({
index: the._taskIndex,
task: fn
});
return the;
},
/**
* 添加任务回退方法
* @param rollback {Function} 回退、中止方法
* @returns {Howdo}
*/
rollback: function (rollback) {
var the = this;
if (the._taskIndex > -1 && isFunction(rollback)) {
the._contextList[the._taskIndex].rollback = rollback;
}
return the;
},
/**
* 添加任务回退方法
* @param abort {Function} 回退、中止方法
* @returns {Howdo}
*/
abort: function (abort) {
var the = this;
if (the._taskIndex > -1 && isFunction(abort)) {
the._contextList[the._taskIndex].abort = abort;
}
return the;
},
/**
* 直到 结束
* @param fn {Function} 验证函数
* @param [ignoreError] {Boolean} 是否忽略错误
* @returns {Howdo}
*/
until: function (fn, ignoreError) {
var the = this;
if (!isFunction(fn)) {
throw new TypeError('until `condition` must be a function');
}
the._untilCondition = fn;
the._ignoreErr = ignoreError !== false;
return the;
},
/**
* 循环分配任务
* @param {Object} object 对象或者数组
* @param {Function} callback 回调
* @return Howdo
*/
each: function (object, callback) {
var howdo = this;
each(object, task);
function task(key, val) {
howdo = howdo.task(function () {
var args = [key, val];
args = args.concat(slice.call(arguments));
callback.apply(val, args);
});
}
return howdo;
},
/**
* 跟着做,任务串行执行
* 链式结束
* @param [callback] {Function} 回调
*/
follow: function (callback) {
var the = this;
if (the._executed) {
return the;
}
if (!isFunction(callback)) {
callback = noop;
}
the._allCallback = callback;
the._executed = true;
var current = 0;
var count = the._taskList.length;
var args = [];
// 串行回退已成功的任务
var rollbackTask = function () {
each(the._contextList, function (index, context) {
if (!context) {
return false;
}
if (!context.error && isFunction(context.rollback)) {
context.rollback.call(context);
context.error = null;
}
});
};
nextTick(function () {
if (!count) {
the._fixCallback();
return the;
}
(function _follow() {
var fn = function () {
args = slice.call(arguments);
var error = args[0];
// has error
if (error && !the._ignoreErr) {
context.error = error;
rollbackTask();
return the._fixCallback(error);
}
current++;
var canStop = false;
if (the._untilCondition) {
// 一轮任务完成
if (current === count) {
canStop = the._untilCondition.apply(_global, args.slice(1));
current += canStop ? 0 : -count;
}
} else {
canStop = current === count;
}
if (canStop) {
rollbackTask();
the._fixCallback.apply(the, args);
} else if (current < count) {
args.shift();
_follow();
}
};
args.unshift(fn);
var task = the._taskList[current];
var context = the._contextList[current];
task.apply(context, args);
})();
});
return the;
},
/**
* 一起做,任务并行执行
* 链式结束
* @param [callback] {Function} 回调
*/
together: function (callback) {
var the = this;
if (the._executed) {
return;
}
if (!isFunction(callback)) {
callback = noop;
}
the._allCallback = callback;
the._executed = true;
var doneLength = 0;
var count = the._taskList.length;
var taskData = [];
var hasCallback = false;
var i = 0;
// 中止未完成的 task
var abortTask = function () {
hasCallback = true;
each(the._contextList, function (index, context) {
if (!context.done && isFunction(context.abort)) {
context.abort.call(context);
}
});
};
// 回退已经完成的 task
var rollbackTask = function () {
each(the._contextList, function (index, context) {
if (context.done && isFunction(context.rollback)) {
context.rollback.call(context);
}
});
};
nextTick(function () {
if (!count) {
hasCallback = true;
the._fixCallback();
return the;
}
for (; i < count; i++) {
_doTask(i, the._taskList[i]);
}
function _doTask(index, task) {
var context = the._contextList[index];
var fn = function () {
if (hasCallback) {
return;
}
var args = slice.call(arguments);
var ret = [];
var j = 0;
context.done = true;
// has Error
if (args[0] && !the._ignoreErr) {
abortTask();
rollbackTask();
return the._fixCallback(args[0]);
}
var canStop = false;
var value = args.slice(1);
if (the._untilCondition) {
canStop = the._untilCondition.apply(_global, value);
if (canStop) {
doneLength = count;
} else {
doneLength++;
}
if (doneLength === count) {
if (canStop) {
ret = ret.concat(value);
}
ret.unshift(null);
abortTask();
the._fixCallback.apply(the, ret);
}
} else {
doneLength++;
taskData[index] = value;
if (doneLength === count) {
for (; j < taskData.length; j++) {
ret = ret.concat(taskData[j]);
}
ret.unshift(null);
abortTask();
the._fixCallback.apply(the, ret);
}
}
};
task.call(context, fn);
}
});
return the;
},
/**
* 正常回调
* @param callback
*/
try: function (callback) {
var the = this;
if (isFunction(callback)) {
the._tryCallbackList.push(callback);
}
return the;
},
/**
* 异常回调
* @param callback
*/
catch: function (callback) {
var the = this;
if (isFunction(callback)) {
the._catchCallbackList.push(callback);
}
return the;
},
/**
* 修正回调
* @param err
* @private
*/
_fixCallback: function (err/*arguments*/) {
var the = this;
var args = slice.call(arguments, 1);
the._allCallback.apply(_global, arguments);
if (err) {
return each(the._catchCallbackList, function (i, callback) {
callback.call(_global, err);
});
}
each(the._tryCallbackList, function (i, callback) {
callback.apply(_global, args);
});
}
};
//Howdo.prototype.serial = Howdo.prototype.follow;
//Howdo.prototype.parallel = Howdo.prototype.together;