在写node.js的时候,经常会遇到要去数据库的多个地方取得多个数据,然后才能进行下一步的操作的情况。如果是线性执行的语言,通常的做法是一条一条去取,全部取到之后再进行下一步操作。然而在node里面,因为是基于事件的,所以只能够一层一层的在回调函数里面嵌套进去。取到第一个数据之后,执行回调函数去取第二条数据,然后再执行回调函数。
对于node来说,这样是效率低下的,因为很多数据并不是需要先取完A再去取B的,而是可以同时取AB的,然而通过回调函数嵌套的做法,只能够顺序去取数据(和线性执行的语言一样),同时会导致代码嵌套过多难以阅读。
解决这个问题的一个方法是通过事件的方式。遇到要取多个数据的时候,就让它们都去取数据,取到数据之后,抛出一个取到数据的事件,同时开一个监听函数,监听这些事件,当所有的数据都取到了,监听函数接收到了所有数据的时候,开始执行后面的步骤。这样可以尽可能的让数据获取能够同步进行(如果有限制关系:如先取到文章才能取到文章的评论,同样可以用事件的方式解决,不过依旧要按照顺序去获取数据),同时代码也会简洁易懂,没有多层的嵌套。
然而想法是美好的,node.js提供的eventemit却并不支持监听多个事件。eventProxy模块就是基于事件的对上述问题的一个解决方案。
https://github.com/JacksonTian/eventproxy @朴灵
1.应用场景:
1)最前面举例的时候说到,在写node的时候可以并行处理数据请求,解除多重回调嵌套。
2)在前端页面渲染的时候,可以通过指定当多个事件触发之后(获取模板/获取数据)再开始渲染。
...
2.使用方法
通过assign方法监听多个事件:(只监听一次,触发后就被解除(once))
var EventProxy = require('EventProxy.js').EventProxy;
var event = new EventProxy();
event.assign("1", "2", "3", "4", function(){
for (var i=0; i!=4; ++i){
console.log(arguments[i]);
}
});
console.log("first");
event.emit("1", 1);
event.emit("2", 2);
event.emit("3", 3);
event.emit("3", 3.5);
event.emit("4", 4);
console.log("second");
event.emit("1", 1);
event.emit("2", 2);
event.emit("3", 3);
event.emit("4", 4);
/**
* 结果输出:
* first
* 1
* 2
* 3
* 4
* second
**/ 如果将上面的代码中的assign方法替换成asignAll方法监听多个事件:(持续监听(bind))event.assignAll("1", "2", "3", "4", function(){
for (var i=0; i!=4; ++i){
console.log(arguments[i]);
}
});
/**
* 结果输出:
*first
*1
*2
*3
*4
*second
*1
*2
*3
*4
*1
*2
*3
*4
*1
*2
*3
*4
*1
*2
*3
*4
**/
3.源码实现
eventProxy对于事件的实现方法:
var EventProxy = function () {
this._callbacks = {};
this._fired = {};
};
_callbacks用来存放所有的回调函数,对于一个事件en,每一次绑定都将在_callbacks中的_callbacks[en]这个数组中插入这个回调函数。(_callbacks用object来作为一个集合容器,而_callbacks[ev]用数组方式,在unbind的时候,需要遍历数组去找到要解除绑定的函数)
EventProxy.prototype.bind = EventProxy.prototype.on = EventProxy.prototype.addListener = function(ev, callback) {
var list = this._callbacks[ev] || (this._callbacks[ev] = []);
list.push(callback);
return this;
};
同样,也可以将事件和回调函数解除绑定。
EventProxy.prototype.unbind = EventProxy.prototype.removeListener = function(ev, callback) {
var calls;
if (!ev) {//如果没有输入参数,则清除全部回调函数
this._callbacks = {};
} else if (calls = this._callbacks) {
if (!callback) {//如果输入了event,没输入callback,则删除这个event的所有callbacks
calls[ev] = [];
} else {
var list = calls[ev];
if (!list) return this;
for (var i = 0, l = list.length; i < l; i++) {
if (callback === list[i]) {
list[i] = null; //最后,将找到的这个回调函数指为null(为什么先指为null而到后面等到trigger的时候再删除?)
break;
}
}
}
}
return this;
};
这个函数可以看出,js可以很灵活的通过参数传递的不同来实现不同的效果,比之C++的实现方式要容易。
evnetProxy还提供了once函数,注册的回调函数在执行一次之后就会被unbind。
EventProxy.prototype.once = function (ev, callback) {
var self = this;
this.bind(ev, function () {
callback.apply(self, arguments);
self.unbind(ev, arguments.callee);//callee是当前函数的引用
});
return this;
};
同样和eventemit一样,eventProxy也有事件触发函数emit/fire/trigger,触发eventName事件。
EventProxy.prototype.emit = EventProxy.prototype.fire = EventProxy.prototype.trigger = function(eventName, data, data2) {
var list, calls, ev, callback, args, i, l;
var both = 2; //其实每次都会触发两个事件
if (!(calls = this._callbacks)) return this;
while (both--) {
ev = both ? eventName : 'all'; //第一次是触发eventName事件,第二次触发'all'事件,这个事件在后面提及的assign监听多个事件的时候会起到关键作用。
if (list = calls[ev]) {
for (i = 0, l = list.length; i < l; i++) {
if (!(callback = list[i])) {
list.splice(i, 1); i--; l--; //触发一次这个事件的时候,遍历它,同时将unbind时设置为null的事件从数组删除。
} else {
args = both ? Array.prototype.slice.call(arguments, 1) : arguments;
callback.apply(this, args);//如果是eventName事件,则将data参数传给callback,如果是'all'事件,则把所有参数传递进去。
}
}
}
}
return this;
};
到此为止,已经可以实现node自带的eventEmit的功能了。On/addListener注册事件,加入callback集合中,然后通过emit/fire/trigger来触发这些事件。但是nevetProxy最重要的作用是下面这个_assign方法,它可以同时监听多个事件,只有这些事件都被触发以后才会执行回调函数,同时每一个事件都可以把参数传递给回调函数。
var _assign = function (eventname1, eventname2, cb, once) {
var proxy = this, length, index = 0, argsLength = arguments.length,
callback, events, isOnce, times = 0, flag = {};
// Check the arguments length.
if (argsLength < 3) {
return this;
}
events = [].slice.apply(arguments, [0, argsLength - 2]);
callback = arguments[argsLength - 2];
isOnce = arguments[argsLength - 1];
// Check the callback type.
if (typeof callback !== "function") {
return this;
}
length = events.length;
var bind = function (key) {
var method = isOnce ? "once" : "bind";//函数有两种绑定方法,绑定一次或者绑定多次
proxy[method](key, function (data) {
proxy._fired[key] = proxy._fired[key] || {};
proxy._fired[key].data = data;
if (!flag[key]) {
flag[key] = true;
Times++; //计数器,每有一个事件被触发,计数器+1
}
});
};
for (index = 0; index < length; index++) {//对所有要监听事件调用上面的bind方法,为其绑定一个回调函数,当触发这个事件的时候,把数据保存下来。
bind(events[index]);
}
var all = function () { //当计数器的计数等于事件数目(说明所有事件都被触发了一次),就把所有获取到的数据汇总,然后交给真正的回调函数cb执行。
if (times < length) {
return;
}
var data = [];
for (index = 0; index < length; index++) {
data.push(proxy._fired[events[index]].data);
}
if (isOnce) { //如果只监听一次,则解除这个'all'的监听。反之则继续监听这个事件。
proxy.unbind("all", all);
}
callback.apply(null, data);
};
proxy.bind("all", all); //绑定'all'事件和上面的all函数,而每当一个事件被fire的时候,都会抛出一个'all'事件,使得all函数被调用,去检查是否所有被监听的事件都被fire过了。
};
上面的函数被下面两个接口函数调用,实现两种不同的功能。
//当所有事件都发生之后,执行一次cb,然后撤销监听。 EventProxy.prototype.assign = function (eventname1, eventname2, cb) {
var args = [].slice.call(arguments);
args.push(true);
_assign.apply(this, args);
return this;
}; //当所有事件都发生之后,执行一次cb,然后当任何一个事件再发生的时候,就会继续执行cb。
EventProxy.prototype.assignAll = EventProxy.prototype.assignAlways = function () {
var args = [].slice.call(arguments);
args.push(false);
_assign.apply(this, args);
return this;
};
assign运用:当在数据库取数据的时候,可以同时去取N个数据,当得到所有的结果之后,再执行后续的处理。
Assignall运用:
1.例如一个股票软件,需要定时更新数据。在模板ok之前来的数据也没有用的。只有在数据和模板都ok之后,才第一次渲染, 这之后,数据每次更新都需要重新渲染。
2.对现有结果进行搜索的场景,之前在过滤数据之后,需要更新dom。
通过eventProxy,可以充分的发挥js的异步/基于事件的特性,同步进行多个操作。同时也可以让代码更加简洁自然,消除多重嵌套。
posted on 2011-08-18 15:24
dead_horse 阅读(3341)
评论(0) 编辑 收藏 引用 所属分类:
node.js