一步一步实现基于Task的Promise库(二)all和any方法的设计和实现

时间:2022-03-02 19:36:56

上一篇中我们已经初步完成了Task类,如果仅仅是这些,那么没有多大意义,因为网上这类js库有很多,现在我们来些更复杂的使用场景。

如果我们现在有这样一个需求:我们要先读取aa.txt的内容,然后去后台解析,同时bb.txt也要读取解析,然后当两个文件都解析完了,我们还要合并两部分内容存到cc.txt中,最后发个通知说ok了。。需求很变态,但是我还是想问有没有好的办法呢?按照最原始的嵌套回调的写法好像不是那么容易了,因为你没法知道aa.txt和bb.txt两个文件的读取解析谁先完成,所以你除了要关注逻辑本身还得花费一些功夫在这个谁先谁后上面,如果需求变了。。。然后就没有然后了,想想我就要吐血。

那么回到上一章的Task类,Task有没有办法解决这个问题呢?回答是有,先看下面的代码:

 var taskExp_1 = new Task(readFile, ["aa.txt"]).then(resolveFile, ["/service/fileResolve.ashx?file=aa.txt"]);
var taskExp_2 = new Task(readFile, ["bb.txt"]).then(resolveFile, ["/service/fileResolve.ashx?file=bb.txt"]);
var taskExp_3 = new Task(taskExp_1, taskExp_2).all(writeFile, ["cc.txt"]).then(sendMail).start();

如同需求描述的一样,taskExp_1,taskExp_2分别描述了aa.txt和bb.txt的读取和解析,taskExp_3的构造方法里面包括了taskExp_1,taskExp_2,后面的all方法表示需要taskExp_1和taskExp_2都完成才能执行writeFile,最后才是sendMail。这样一来彻底解决了这类需求带来的复杂性,如果把all改成any表示taskExp_1和taskExp_2完成其中一个就能执行writeFile。

在实现all和any方法之前,先看一看我们将要面临哪些问题:

  1. 现在new Task(),then(),all(),any()方法的形参可以是多个异步操作了,并且每个异步操作可以是一个方法和它的参数,也可以是一个Task。
  2. 既然这些方法的形参可以是Task,那么我们理所当然要支持Task的完成通知。
  3. 一组异步操作的返回值如何传递到下一组异步操作 ,对于all方法如何接收前一组异步操作的所有输出参数呢?在上一章里我们通过this.param接收参数,这里对于then,any方法同样如此,因为最终它们只会有一个异步操作的输出参数传递到下一个异步操作,all方法比较特殊,我斟酌好久决定使用this.param[0],this.param[1]... 来接收同组的不同异步操作的输出参数
  4. 当不同状态的Task作为参数传递到另一个Task中时(未开始执行的,正在执行的,执行完成的),应该怎么处理?我们还得支持Task的状态管理。

继续上一章所写的Task类来扩展这两个方法,现在我们以一个标准的js库形式来书写实现细节,这个库命名为Task.js:

 (function(){
var isFunction = function(target){
return target instanceof Function;
};
var isArray = function(target){
return target instanceof Array;
}; //自定义事件管理(代码摘抄自http://www.cnblogs.com/dolphinX/p/3254017.html)
var EventManager = function(){
this.handlers = {};
};
EventManager.prototype = {
constructor: EventManager,
addHandler: function(type, handler){
if(typeof this.handlers[type] == 'undefined'){
this.handlers[type] = new Array();
}
this.handlers[type].push(handler);
},
removeHandler: function(type, handler){
if(this.handlers[type] instanceof Array){
var handlers = this.handlers[type];
for(var i=0; i<handlers.length; i++){
if(handler[i] == handler){
handlers.splice(i, 1);
break;
}
}
}
},
trigger: function(type, event){
/*
if(!event.target){
event.target = this;
}
*/
if(this.handlers[type] instanceof Array){
var handlers = this.handlers[type];
for(var i=0; i<handlers.length; i++){
handlers[i](event);
}
}
}
}; //WorkItem的参数和Task一样,有下面几种形式,不过最终存到subItems里面的元素只有两种类型.一种是方法和它的参数,一种是Task对象
//1.(func, [funcArg1, funcArg2])
//2.(func, funcArg1, funcArg2, ....)
//3.(task1, task2, ....)
//4.([func, funcArg1, funcArg2, ....]
// ,[func, [funcArg1, funcArg2]]
// ,task1
// , ....
// )
var WorkItem = function(arrayArgs){
//WorkItem其实是一组异步操作的集合,_subItems就是这个集合
var _subItems = [];
var _checkFunc = function(args){
if(isFunction(args[0])){
if(args.length == 2 && isArray(args[1])){
_subItems.push({'isFunc': true, 'func': args[0], 'args': args[1]});
}
else{
_subItems.push({'isFunc': true, 'func': args[0], 'args': args.slice(1)});
}
return true;
}
return false;
};
var _checkTask = function(task){
if(task instanceof Task){
_subItems.push({'isFunc': false, 'task': task});
}
};
//这里是对形参的检测,看看是否满足上面的4种形式
if(!_checkFunc(arrayArgs)){
for(var i=0; i<arrayArgs.length; i++){
if(!_checkFunc(arrayArgs[i])){
_checkTask(arrayArgs[i]);
}
}
} //开始执行SubItem
var _startSubItem = function(subItemIndex, context){
var subItem = _subItems[subItemIndex];
//如果subItem是方法和它的参数
if(subItem.isFunc){
//先获取方法的上下文环境(就是方法里面的this)
var workItemCxt = context.getWorkItemContext(subItemIndex);
//再执行方法
subItem.func.apply(workItemCxt, subItem.args);
}
//如果subItem是Task对象
else{
//如果task已经完成
if(subItem.task.getStatus() == TaskStatus.finished){
context.end(subItem.task.getOutput(), subItemIndex)
}
else{
//先注册Task对象的完成事件
subItem.task.finished(function(output){
context.end(output, subItemIndex);
});
//再启动这个task
subItem.task.start(context.inputParams);
}
}
};
this.condition = "";
//开始执行WorkItem
this.start = function(context){
context.setItemsCount(_subItems.length);
for(var i=0; i<_subItems.length; i++){
_startSubItem(i, context);
}
}
}; //异步操作上下文
var Context = function(endCallback, inputParams){
var _this = this;
//Workitem里面的每一个Item会按各自的索引把返回值存到_rawOutputParams里,这样做的目的是为了方便后续操作的先决条件判断
var _rawOutputParams = [];
//子Item个数
var _itemCount;
//先决条件的判断方法集合,判断的同时也会更新outputParams
var _condition = {
then: function(){
_this.outputParams = _rawOutputParams[0].value;
return true;
},
all: function(){
_this.outputParams = [];
for(var i=0; i<_itemCount; i++){
if(_rawOutputParams[i]){
_this.outputParams[i] = _rawOutputParams[i].value;
}
else{
return false;
}
}
return true;
},
any: function(){
for(var i=0; i<_itemCount; i++){
if(_rawOutputParams[i]){
_this.outputParams = _rawOutputParams[i].value;
return true;
}
}
return false;
}
}; //异步操作的输入操作
this.inputParams = inputParams;
//最终异步操作上下文结束时的输出参数(一般是一个操作的输出参数,all条件的输出参数比较特殊,是一个数组,每一个元素对应每一个子Workitem的输出参数)
this.outputParams = null;
this.setItemsCount = function(itemCount){
_itemCount = itemCount;
};
//测试_rawOutputParams对于key条件是否通过
this.testCondition = function(key){
return _condition[key]();
};
//索引为index的子Workitem完成时会调用这个方法
this.end = function(output, index){
_rawOutputParams[index] = {
value: output
};
if(endCallback){
endCallback(output);
}
};
//生成一个子Workitem执行时的上下文环境
this.getWorkItemContext = function(index){
//这个是子Item的上下文对象(就是this)
return {
//传递给上下文的参数
param: _this.inputParams,
//调用end方法告知异步操作的完成
end: function(output){
_this.end(output, index);
}
};
};
}; //Task的状态
var TaskStatus = {
//未开始
pending: 0,
//正在进行
doing: 1,
//已完成
finished: 2
}; //不定义具体形参,直接使用arguments
window.Task = function(){
var _status = TaskStatus.pending;
var _wItemQueue = [], _currentItem, _currentContext;
var _eventManager = new EventManager();
var _output;
//初始化一个WorkItem,并添加到执行队列中
var _initWorkItem = function(args){
var arrayArgs = [];
for(var i=0; i<args.length; i++){
arrayArgs[i] = args[i];
}
var item = new WorkItem(arrayArgs);
_wItemQueue.push(item);
return item;
};
//设置item的先决条件
var _setItemCondition = function(item, condition){
if(condition){
item.condition = condition;
}
};
//试着执行下一个异步操作,如果这个操作满足他的先决条件,那就执行;如果已经式最后一个异步操作了,就触发完成事件
var _tryDoNextItem = function(output){
var next = _getCurNextItem();
if(next){
if(_currentContext.testCondition(next.condition)){
_currentItem = next;
_doCurrentItem();
}
}
else{
_status = TaskStatus.finished;
_output = output;
_eventManager.trigger("finish", output);
}
};
//执行当前的Workitem
var _doCurrentItem = function(contextParam){
if(contextParam) {
_currentContext = new Context(_tryDoNextItem, contextParam);
}
else{
if(_currentContext){
_currentContext = new Context(_tryDoNextItem, _currentContext.outputParams);
}
else{
_currentContext = new Context(_tryDoNextItem);
}
}
_currentItem.start(_currentContext);
};
//获取下一个异步操作,如果已经是最后一个了返回undefined
var _getCurNextItem = function(){
var i=0;
for(; i<_wItemQueue.length; i++){
if(_currentItem == _wItemQueue[i]){
break;
}
}
return _wItemQueue[i + 1];
};
_currentItem = _initWorkItem(arguments); //获取Task当前状态
this.getStatus = function(){
return _status;
};
//获取Task完成时的输出参数
this.getOutput = function(){
return _output;
};
//注册完成事件
this.finished = function(callback){
if(callback){
_eventManager.addHandler("finish", callback);
}
};
//任务开始(把do改成start是因为在ie下有问题,ie会把do当做保留字)
//contextParam是一个上下文参数,可以在异步方法中通过this.Param引用
this.start = function(contextParam){
if(_status == TaskStatus.pending){
_status = TaskStatus.doing;
_doCurrentItem(contextParam);
}
return this;
};
this.then = function(){
var workItem = _initWorkItem(arguments);
_setItemCondition(workItem, 'then');
return this;
};
this.all = function(){
var workItem = _initWorkItem(arguments);
_setItemCondition(workItem, 'all');
return this;
};
this.any = function(){
var workItem = _initWorkItem(arguments);
_setItemCondition(workItem, 'any');
return this;
};
};
})();

除了之前的Task类之外,还添加了WorkItem类用来表示一组异步操作。代码有点多,细节请看注释。

最后给一个demo:

 <!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<script src="jquery-latest.js" type="text/javascript"></script>
<script type="text/javascript">
//promise
//读取文件的原始内容
var readFile = function(fileName){
var _this = this;
window.setTimeout(function(){
var rawContent = "xxxxxxxx (" + fileName + ")";
console.log("read '" + fileName + "' complete. rawContent is " + rawContent);
_this.end(rawContent);
}, 1000);
};
//请求服务器来解析原始内容,得到真正的内容
var resolveFile = function(serverUrl){
var _this = this;
var rawContent = _this.param;
window.setTimeout(function(){
var realContent = "Greeting (" + serverUrl + ")";
console.log("resolve file complete. realContent is " + realContent);
_this.end(realContent);
}, 1500);
};
//把真正的内容写入一开始的文件
var writeFile = function (fileName) {
var _this = this;
window.setTimeout(function(){
console.log("writeBack1 param[0] is " + _this.param[0] + " ;param[1] is " + _this.param[1]);
_this.end();
}, 2000);
};
var sendMail = function(){
var _this = this;
window.setTimeout(function(){
console.log("sendMail finished");
_this.end();
}, 1000);
}; //问题:
//1.WorkItem的参数多样化 (一些没有意义的使用形式?)
//2.new Task(),then,all,any方法参数可以接收Task对象 (Task完成的事件通知?)
//3.一组异步操作的返回值如何传递到下一组异步操作 (对于all方法如何接收前一组异步操作的所有输出参数)
//4.当不同状态的Task作为参数传递到另一个Task中时(未开始执行的,正在执行的,执行完成的),应该怎么处理 (Task状态管理)
(function(){
var isFunction = function(target){
return target instanceof Function;
};
var isArray = function(target){
return target instanceof Array;
}; //自定义事件管理(代码摘抄自http://www.cnblogs.com/dolphinX/p/3254017.html)
var EventManager = function(){
this.handlers = {};
};
EventManager.prototype = {
constructor: EventManager,
addHandler: function(type, handler){
if(typeof this.handlers[type] == 'undefined'){
this.handlers[type] = new Array();
}
this.handlers[type].push(handler);
},
removeHandler: function(type, handler){
if(this.handlers[type] instanceof Array){
var handlers = this.handlers[type];
for(var i=0; i<handlers.length; i++){
if(handler[i] == handler){
handlers.splice(i, 1);
break;
}
}
}
},
trigger: function(type, event){
/*
if(!event.target){
event.target = this;
}
*/
if(this.handlers[type] instanceof Array){
var handlers = this.handlers[type];
for(var i=0; i<handlers.length; i++){
handlers[i](event);
}
}
}
}; //WorkItem的参数和Task一样,有下面几种形式,不过最终存到subItems里面的元素只有两种类型.一种是方法和它的参数,一种是Task对象
//1.(func, [funcArg1, funcArg2])
//2.(func, funcArg1, funcArg2, ....)
//3.(task1, task2, ....)
//4.([func, funcArg1, funcArg2, ....]
// ,[func, [funcArg1, funcArg2]]
// ,task1
// , ....
// )
var WorkItem = function(arrayArgs){
//WorkItem其实是一组异步操作的集合,_subItems就是这个集合
var _subItems = [];
var _checkFunc = function(args){
if(isFunction(args[0])){
if(args.length == 2 && isArray(args[1])){
_subItems.push({'isFunc': true, 'func': args[0], 'args': args[1]});
}
else{
_subItems.push({'isFunc': true, 'func': args[0], 'args': args.slice(1)});
}
return true;
}
return false;
};
var _checkTask = function(task){
if(task instanceof Task){
_subItems.push({'isFunc': false, 'task': task});
}
};
//这里是对形参的检测,看看是否满足上面的4种形式
if(!_checkFunc(arrayArgs)){
for(var i=0; i<arrayArgs.length; i++){
if(!_checkFunc(arrayArgs[i])){
_checkTask(arrayArgs[i]);
}
}
} //开始执行SubItem
var _startSubItem = function(subItemIndex, context){
var subItem = _subItems[subItemIndex];
//如果subItem是方法和它的参数
if(subItem.isFunc){
//先获取方法的上下文环境(就是方法里面的this)
var workItemCxt = context.getWorkItemContext(subItemIndex);
//再执行方法
subItem.func.apply(workItemCxt, subItem.args);
}
//如果subItem是Task对象
else{
//如果task已经完成
if(subItem.task.getStatus() == TaskStatus.finished){
context.end(subItem.task.getOutput(), subItemIndex)
}
else{
//先注册Task对象的完成事件
subItem.task.finished(function(output){
context.end(output, subItemIndex);
});
//再启动这个task
subItem.task.start(context.inputParams);
}
}
};
this.condition = "";
//开始执行WorkItem
this.start = function(context){
context.setItemsCount(_subItems.length);
for(var i=0; i<_subItems.length; i++){
_startSubItem(i, context);
}
}
}; //异步操作上下文
var Context = function(endCallback, inputParams){
var _this = this;
//Workitem里面的每一个Item会按各自的索引把返回值存到_rawOutputParams里,这样做的目的是为了方便后续操作的先决条件判断
var _rawOutputParams = [];
//子Item个数
var _itemCount;
//先决条件的判断方法集合,判断的同时也会更新outputParams
var _condition = {
then: function(){
_this.outputParams = _rawOutputParams[0].value;
return true;
},
all: function(){
_this.outputParams = [];
for(var i=0; i<_itemCount; i++){
if(_rawOutputParams[i]){
_this.outputParams[i] = _rawOutputParams[i].value;
}
else{
return false;
}
}
return true;
},
any: function(){
for(var i=0; i<_itemCount; i++){
if(_rawOutputParams[i]){
_this.outputParams = _rawOutputParams[i].value;
return true;
}
}
return false;
}
}; //异步操作的输入操作
this.inputParams = inputParams;
//最终异步操作上下文结束时的输出参数(一般是一个操作的输出参数,all条件的输出参数比较特殊,是一个数组,每一个元素对应每一个子Workitem的输出参数)
this.outputParams = null;
this.setItemsCount = function(itemCount){
_itemCount = itemCount;
};
//测试_rawOutputParams对于key条件是否通过
this.testCondition = function(key){
return _condition[key]();
};
//索引为index的子Workitem完成时会调用这个方法
this.end = function(output, index){
_rawOutputParams[index] = {
value: output
};
if(endCallback){
endCallback(output);
}
};
//生成一个子Workitem执行时的上下文环境
this.getWorkItemContext = function(index){
//这个是子Item的上下文对象(就是this)
return {
//传递给上下文的参数
param: _this.inputParams,
//调用end方法告知异步操作的完成
end: function(output){
_this.end(output, index);
}
};
};
}; //Task的状态
var TaskStatus = {
//未开始
pending: 0,
//正在进行
doing: 1,
//已完成
finished: 2
}; //不定义具体形参,直接使用arguments
window.Task = function(){
var _status = TaskStatus.pending;
var _wItemQueue = [], _currentItem, _currentContext;
var _eventManager = new EventManager();
var _output;
//初始化一个WorkItem,并添加到执行队列中
var _initWorkItem = function(args){
var arrayArgs = [];
for(var i=0; i<args.length; i++){
arrayArgs[i] = args[i];
}
var item = new WorkItem(arrayArgs);
_wItemQueue.push(item);
return item;
};
//设置item的先决条件
var _setItemCondition = function(item, condition){
if(condition){
item.condition = condition;
}
};
//试着执行下一个异步操作,如果这个操作满足他的先决条件,那就执行;如果已经式最后一个异步操作了,就触发完成事件
var _tryDoNextItem = function(output){
var next = _getCurNextItem();
if(next){
if(_currentContext.testCondition(next.condition)){
_currentItem = next;
_doCurrentItem();
}
}
else{
_status = TaskStatus.finished;
_output = output;
_eventManager.trigger("finish", output);
}
};
//执行当前的Workitem
var _doCurrentItem = function(contextParam){
if(contextParam) {
_currentContext = new Context(_tryDoNextItem, contextParam);
}
else{
if(_currentContext){
_currentContext = new Context(_tryDoNextItem, _currentContext.outputParams);
}
else{
_currentContext = new Context(_tryDoNextItem);
}
}
_currentItem.start(_currentContext);
};
//获取下一个异步操作,如果已经是最后一个了返回undefined
var _getCurNextItem = function(){
var i=0;
for(; i<_wItemQueue.length; i++){
if(_currentItem == _wItemQueue[i]){
break;
}
}
return _wItemQueue[i + 1];
};
_currentItem = _initWorkItem(arguments); //获取Task当前状态
this.getStatus = function(){
return _status;
};
//获取Task完成时的输出参数
this.getOutput = function(){
return _output;
};
//注册完成事件
this.finished = function(callback){
if(callback){
_eventManager.addHandler("finish", callback);
}
};
//任务开始(把do改成start是因为在ie下有问题,ie会把do当做保留字)
//contextParam是一个上下文参数,可以在异步方法中通过this.Param引用
this.start = function(contextParam){
if(_status == TaskStatus.pending){
_status = TaskStatus.doing;
_doCurrentItem(contextParam);
}
return this;
};
this.then = function(){
var workItem = _initWorkItem(arguments);
_setItemCondition(workItem, 'then');
return this;
};
this.all = function(){
var workItem = _initWorkItem(arguments);
_setItemCondition(workItem, 'all');
return this;
};
this.any = function(){
var workItem = _initWorkItem(arguments);
_setItemCondition(workItem, 'any');
return this;
};
};
})(); var taskExp_1 = new Task(readFile, ["aa.txt"]).then(resolveFile, ["/service/fileResolve.ashx?file=aa.txt"]);
var taskExp_2 = new Task(readFile, ["bb.txt"]).then(resolveFile, ["/service/fileResolve.ashx?file=bb.txt"]);
var taskExp_3 = new Task(taskExp_1, taskExp_2).all(writeFile, ["cc.txt"]).then(sendMail).start(); </script>
</body>
</html>