先贴代码,这一段是小东的版本;现在我做了一些修改;
function EventDispatcher(){
var that = this;
this.dispatch = function(request) {
if (typeof request.action == 'function')
return request.action();
var controllerName = request['controller'].toLowerCase().capitalize() + 'Controller';
var actionName = request['action'].toLowerCase().capitalize() + 'Action';
var controller = EventFront.getControllerObj(controllerName);
controller.preDispatch();
try {
controller[actionName].call(controller);
} catch(e) {
console.log(e);
}
controller.postDispatch();
}
}
var EventFront = (function() {
var _plugins = [];
var _request = null;
var _dispatcher = new EventDispatcher();
var _busy = false;
var _requests = [];
var _empty = false;
var _dispatched = false;
return {
isBusy: function() {
return _busy;
},
setRequest: function(request) {
this.clean();
_requests.push(request);
return this;
},
getRequest: function() {
return _request;
},
getController: function() {
return _request.controller;
},
getAction: function() {
return _request.action.toLowerCase();
},
isDispatched: function() {
return _dispatched;
},
registerPlugin: function(plugin) {
Interface.ensureImplements(plugin, InterfaceEventPlugin);
_plugins.push(plugin);
return this;
},
unregisterPlugin: function (plugin) {
_plugins.remove(plugin);
return this;
},
// 当积累了req时, 非常快的执行了循环调用!
// 出现了action并发的情况!!
// activeIndex等于相同的数字了!!
dispatch: function() {
if (_requests.length == 0) {
_empty = true;
return;
}
//console.log("请求进入: _busy=" + _busy);
if (_busy == true) {
//console.log("排队:" + _requests.length );
return;
}
_dispatched = false;
_empty = false;
_request = _requests.shift();
//console.info("分发")
if (_busy == false) {
_busy = true;
}
_plugins.forEach(function (plugin) {
plugin.preDispatch();
});
// Ajax
_dispatcher.dispatch.call(_dispatcher, _request);
_plugins.forEach(function (plugin) {
plugin.postDispatch();
});
_dispatched = true;
/* if (_empty == true) {
this.done();
} */
},
done: function() {
//console2.log('_busy = false;')
_busy = false;
this.dispatch();
},
clean: function() {
_plugins = [];
//_request = null;
},
redirect: function(request) {
_request = request;
//this.setRequest(request);
//this.dispatch(request);
},
setEmpty: function(empty) {
_empty = empty;
}
}
})();
一些现有的问题
为了给读者说明, 先解释一下"一组action", "request"的概念;
- 一个action可能是包含数个异步动作(函数)的执行过程,当然也可能只是一个函数,不会引发其他的异步过程;
- request,是推入到EventFront对象内的request队列中的对象,用request把执行的action封装一下;
EventFront被dispatch分发一次时,所设置的request被执行,在该被执行的action的最后一个函数过程中,需要调用Main.done()通知队列中可以被推入下一个request;
现在的问题是,上上述的代码中在Main.done()被调用之前被推入的新的request会被丢弃(busy == true时直接返回),实际上EventFront只是允许单个request对象;
在每一个action的一组动作被调度执行到最后一个动作之间,可能被调度的有以下几种可能性:
- 新的keydown事件引发的新的动作,在当前的实现中,这必然导致新的dispatch被调用,产生一个新的request对象;
- 当前这个EventDispatch对象的dispatch方法中调用的相关controller的action方法中的后继的定时器方法或注册的异步事件的方法;
- 几个plugin的preDispatch, postDispatch当中的异步动作;
以上3中可能性被调度的次序是不确定的,是宏观上并行的过程;这样需要注意这些并行发生且调度顺序不确定的子过程之间的逻辑一致性的问题;请参见下面的"一些限制和注意"的部分;
在这几种可能性中,如果有一个新的request被推入EventFront,由于busy == true, 则原有代码那部分直接返回,新的request就被丢失了;我希望EventFront的request队列可以保持每次推入的request对象;
讨论一下preDispatch, action, postDispatch这三个过程中可能发生的执行的语义,即我希望它们当中执行我们系统中的什么逻辑,以及一些限制;
preDispatch:如果preDispatch中包含异步过程,或者定义新的EventFront.dispatch,这个动作也是在当前request被执行完调用Main.done()才被执行,因此一般不要再preDispatch中使用异步过程或者定义新的request,来发动EventFront.dispatch;
postDispatch,现有的postDispatch是用于切换状态,把就的控件隐藏,新的控件显示,但由于某些控件的每次切换需要从服务器端拉取数据,这是一个异步过程;在ajax回调函数被调度之前,系统可能会调用来自键盘keydown消息,产生controller的action动作;而此时当前的action也可能处于未完成的执行中,这是上述的宏观并行场景,我希望在postDispatch中发起的动作,能被顺序管理,action被并且不会丢失keydown及其它的action;
一些限制和注意:
- Main.done()中的定时器。why? 在当前的实现中,是为了保证当前的action,postDispatch等函数至少被执行完毕一次,才通知EventFront允许下一个request进来;否则,分析一下如果action是一个单一不包含异步过程,但仍需在最后调用Main.done()来通知EventFront时会怎么样?
- 一般来说,preDispatch和postDispatch只是一些副作用的动作,注意不要在其中直接调用Main.done()或者EventFront.done(), 在preDispatch中调用直接调用EventFront.done在当前的实现下会造成无出口递归;原本的设计并不是这样准备的;
- 在RoutePlugin的postDispatch中,切换状态,由于前述的一致性的问题,会读取和设置Main.state(), 如果不能解决调度顺序的问题,则如果在某个controller的action也设置例如Main.state('video'),则可能出现问题;由于action中必须会设置channelObj,videoObj等细节粒度的状态,如果postDispatch中依赖这些,也会出现问题。当然这只在无法顺序管理的情况下;
-
目前的registerPlugin也有一些潜在的问题,即每次setRequest的clean方法会为这个reqeust新生成request数组,否则每次registerPlugin(new RoutePlugin())的方法会推入重复的plugin;并且某些特定的执行顺序会清除以前注册plugin,这似乎不太符合plugin设计初衷;
我希望这个_plugins池内仅仅按照类型区分每种plugin,避免重复生成;
由此总结一下,我做出的修改:
- 可以推入多个request;
- 在dispatch的controller的action动作中和postDispatch中都有可能发出EventFront.setRequest和EventFront.dispatch,向队列加入下一个执行动作;
- registerPlugin方法注册各种plugin,按照plugin的类型区分保存plugin的实例,不需要每次都new plugin。特别是在允许推入多个request的情况下,不要每次推入清除以前的注册;还有如何注册plugin的方式的修改,即在用request对一组action进行封装的时候,指定这一次被执行的request要执行哪几类plugin;
需要注意的是,任何一次action的最后,都需要调用Main.done()通知EventFront去执行下一次动作;无论包含异步动作与否;
修改
function EventDispatcher(){
var that = this;
this.dispatch = function(request) {
//action to be executed need not to be a method of controller
//it can be a function;
if (typeof request.action == 'function')
return request.action();
var controllerName = request['controller'].toLowerCase().capitalize() + 'Controller';
var actionName = request['action'].toLowerCase().capitalize() + 'Action';
var controller = EventFront.getControllerObj(controllerName);
controller.preDispatch();
try {
controller[actionName].call(controller);
} catch(e) {
console.log(e);
}
controller.postDispatch();
}
}
var EventFront = (function() {
var _request = null;
var _dispatcher = new EventDispatcher();
var _busy = false;
var _requests = [];
var _plugins = [];
var _controllers = {};
var _empty = true;
var _dispatched = false;
return {
getControllerObj : function(name){
if (typeof _controllers[name] == 'undefined')
_controllers[name] = eval('new ' + name + '();');
return _controllers[name];
},
isBusy: function() {
return _busy;
},
setRequest: function(request) {
//this.clean(); //clean plugin?
/* var plugins , that = this;
request.plugins || (plugins = request.plugins = []); */
this.setupPlugins(request.plugins || []);
_requests.push(request);
_empty = false;
return this;
},
getRequest: function() {
return _request;
},
getController: function() {
return _request.controller;
},
getAction: function() {
return _request.action.toLowerCase();
},
setupPlugins: function(requestPlugins){
var _arr = _plugins.slice(0),
index,
that = this;
requestPlugins.forEach(function(each,idx, arr){
index = _arr.indexOf(function(e, i, a){
return e.constructor == each;
});
index != -1 && (
arr[idx] = _arr.splice(index,1)[0], 1
)|| (
that.registerPlugin(arr[idx]),
arr[idx] = _plugins[_plugins.length - 1]
)
});
},
isDispatched: function() {
return _dispatched;
},
registerPlugin: function(plugin) {
//Interface.ensureImplements(plugin, InterfaceEventPlugin);
_plugins.push(new plugin);
return this;
},
unregisterPlugin: function (plugin) {
_plugins.remove(plugin);
return this;
},
dispatch: function(request) {
if (_empty == true && busy == false) {
this.setRequest(request);
//_empty = false;
}
if (_empty = _requests.length == 0)
return;
//console.log("请求进入: _busy=" + _busy);
if (_busy == true) {
//console.log("排队:" + _requests.length );
if (request && _requests.contains(request))
return;
this.setRequest(request);
return;
}
_dispatched = false;
_empty = false;
_request = _requests.shift();
//console.info("分发")
if (_busy == false) {
_busy = true;
}
/* _plugins.forEach(function (plugin) {
plugin.preDispatch();
}); */
_request.plugins.forEach(function(plugin){
plugin.preDispatch();
})
// Ajax
_dispatcher.dispatch.call(_dispatcher, _request);
_request.plugins.forEach(function(plugin){
plugin.postDispatch();
})
/* _plugins.forEach(function (plugin) {
plugin.postDispatch();
}); */
_dispatched = true;
/* if (_empty == true) {
this.done(); //? _empty != true && this.done(); ?
} */
//_empty != true && this.done();
},
done: function() {
_busy = false; //enable each execution of request object in request queue;
this.dispatch();
},
clean: function() {
//_plugins = [];
},
redirect: function(request) {
_request = request;
//this.setRequest(request);
//this.dispatch(request);
},
setEmpty: function(empty) {
_empty = empty;
}
}
})();
经过修改之后,创建request对象和调用dispatch的方式也变得更加简单灵活了;并且保留过去的代码方式,也不会造成错误;
例如:
//case1
var rq1 = {
controller: 'channel',
action: 'notify'
}
var rq2 = {
controller: 'channel',
action: 'right'
}
EventFront.setRequest(rq1)
.setRequest(rq2)
.dispatch(rq);
//case2
EventFront.dispatch({
controller: 'channel',
action: 'enter'
plugins: [RoutePlugin, otherPlugin]
})
//case3
EventFront.dispatch({
action: function(){
console.log(1);
Main.done();
}
});
以上3个例子,说明了几种支持的情况;经过修改,这样EventFront内部的状态也更符合外部定义的语义,例如以下说明:
如case1的调用形式,我们推进2个request,先拽回channellist的数据,初始化显示channel控件之后,然后让焦点在channellist上向右走一步;而且这些步骤是按顺序执行,保证了一致性;
case2则直接调用dispatch,传入定义的request对象。该request是让ChannelController执行enter动作,由于状态表中的设计,enter动作需要把当前控制转换给videoList的,因此我们指明,这个request需要执行RoutePlugin以及otherPlugin(OtherPlugin是示例);setRequest的过程和registerPlugin的过程在EventFront中隐式处理,如果程序封装request时,为当前request所指定的plugin不存在,则会为之创建并保存在_plguins池中;注意程序中做了特别防止为同一个request调用setRequest的保护;不论在封装request的调用处多次调用setRequest还是直接把request对象传递给dispatch,直接调用不会有任何问题;(程序会判断EventFront开始一次执行到队列一次执行空之间的重复request);另外,为每一个request指定它所需要plugin,而plugin的管理和保存也是交给EventFront内部处理,而且指定的时候只用指定它的Constructor定义;
case3则允许队列去调度某一个特定的函数,不必非得和某一个controller联系起来,注意最后的Main.done()的调用;