eventproxy,摆脱node.js异步代码嵌套难以阅读

 首先要感谢eventproxy提供者:JacksonTian(http://weibo.com/shyvo)。node

用node.js去作一些异步的I/O操做时,很容易会写成回调函数深度嵌套,例如:git


  var add= function (v1, v2, v3){
       console.log(v1+v2+v3+'');
    };
var value1,value2,value3
    clinet.get("key1", function (err, data) {
        // do something
value1 = data
        clinet.get("key2", function ( err, data ) {
            // do something
           value2=data
            clinet.get("key3", function ( err, data ) {
                //do something
value3 = data
                add(value1, value2, value3);
            });
        });
    });
 
若是咱们使用了eventproxy这个插件,则能够更多关心业务,去掉深度嵌套,而且在一些状况下显著提升效率。
在异步操做返回结果互相不耦合的状况下,使用eventproxy会提升性能。
代码能够改写为:
 
 var EventProxy = require('./eventproxy');
 var proxy = new EventProxy();
 var add= function (v1, v2, v3){
       console.log(v1+v2+v3+'');
    };
proxy.assign("v1", "v2", "v3", add);
    clinet1.get("key1", function (err, data) {
        //do something
        proxy.trigger("v1", data);
    });
   clinet2.get("data", function (err, data) {
        //do something
        proxy.trigger("v2", data);
    });
    clinet3.get("l10n", function (err, data) {
        //do something
        proxy.trigger("v3", data);
    });

这样是否是很酷?先执行 proxy.assign("v1", "v2", "v3", render);   来注册整个异步回调结束后的事件,而后再同时对每一个key去作get操做,这些都是异步的哦,因此会比以前按顺序获取key要快一些,固然这个得配合链接池的应用了,这个例子就使用了clinet1-3这3个redis链接。
整个代码风格易于阅读,并且效率会比以前高出一些,那eventproxy是怎么作到的呢?
咱们打开它的源代码来一探究竟把。
 
assign方法: //入口方法
EventProxy.prototype.assign = function (eventname1, eventname2, cb) {   //eventname1-n 是事件名, cb是最后的回调函数
    var args = [].slice.call(arguments);   //由于arguments只是相似数组的特殊数组,因此须要用slice来将arguments转存到args中
    args.push(true);  //上面的转存就是为了用push方法, 这里将true插入args尾部,这 是干什么呢?答案咱们下段揭晓。
    _assign.apply(this, args);  //调用_assign方法,运行环境是 EventProxy
    return this;
};
_assign函数: //顺藤摸瓜,咱们来看下_assign函数有什么奥秘
var _assign = function (eventname1, eventname2, cb, once) {   //这里同EventProxy.assign函数同样,只是参数的示例
    var proxy = this, length, index = 0, argsLength = arguments.length,  
        callback, events, isOnce, times = 0, flag = {};    
 //这里定义一些变量,将函数调用环境this赋值为proxy,也就是EventProxy
    if (argsLength < 3) {
        return this;        // 若是传入的参数长度错误,则return
    }
    events = [].slice.apply(arguments, [0, argsLength - 2]);   //将参数eventname1-n转存成events 数组
    callback = arguments[argsLength - 2];  //将回调函数(就是例子中的:add函数)存成callback 
    isOnce = arguments[argsLength - 1];   //答案揭晓拉,以前那个true是用来判断是否一次性的,那什么是一次性呢?咱们继续往下看

    if (typeof callback !== "function") {
        return this;
    }
    length = events.length;   //这个length比较重要,是判断须要返回的异步结果总数

    var bind = function (key) {
            var method = isOnce ? "once" : "bind";   //这里会根据是不是一次性来调用不一样的方法
/*
proxy[method]函数说明:
由于咱们以前传递的是true,因此这里调用 EventProxy.prototype.once 这个方法,将key和回调转存进去
EventProxy.prototype.once 这个方法是干什么的呢?咱们下段分析。
回调函数说明:
因为做者没有对 EventProxy. _fired这个属性作任何注释,根据这里的代码发现 EventProxy. _fired属性是存放异步返回结果
存放格式key:value,在本例子中则是{v1:value1, v2:value2, v3: value3},
因此在 proxy.trigger("v1", data); 函数中的第一个参数要和proxy.assign("v1", "v2", "v3", add);的前几个名称相同。
最后flag对象设置key为true。一样做者也没有对flag对象作任何说明,咱们推断出是表示当前这个key已经成功返回
times++表示异步已经返回了多少个结果,注意这里times的做用环境
*/
            proxy[method](key,  function (data) {  
                    proxy._fired[key] = proxy._fired[key] || {};
                    proxy._fired[key].data = data;
                    if (!flag[key]) {
                        flag[key] = true;
                        times++;
                    }
                } );
        };

    for (index = 0; index < length; index++) {
        bind(events[index]);   //每个eventname1-n都调用一次bind函数,并将其值传进去
    }
/*
定义all函数,这个all函数是做为回调函数传入到 EventProxy.prototype.bind中的,咱们先看下这个all函数是干什么的。
*/
     var all = function () {
         if (times < length) {
             return;     //times表明已返回异步结果,length表明共须要返回多少个异步结果,这里若是未所有返回则终止本函数 
         }
         var data = [];  
        for (index = 0; index < length; index++) {
             data.push(proxy._fired[events[index]].data);   //若是异步结果已全返回,按顺序转存EventProxy._fired属性里每一个eventname的值
         }
         if (isOnce) {
             proxy.unbind("all", all);  //又出现了isOnce,若是是true,则接触绑定,unbind函数以后会介绍
         }
        callback.apply(null, data);  //最后执行回调函数,也就是本例中的add(),将data做为参数传给add
     };
     proxy.bind("all", all);     //调用EventProxy.prototype.bind方法
};
EventProxy.prototype.once方法:
/*
这里的ev就是eventname的值,本例中的v1,v2
callback就是上段中的bind函数
紧接着调用 EventProxy.prototype.bind方法,将 eventname的值和一个回调传递过去,这个回调先执行一次callback函数
再调用EventProxy.prototype.unbind函数传递 eventname的值和这个  arguments.callee 即这个回调函数自己。
*/
EventProxy.prototype.once = function (ev, callback) { 
    var self = this;
    this.bind(ev,  function () { 
         callback .apply(self, arguments);    //这里的绿色部分,表明上段2个绿色
        self.unbind(ev, arguments.callee);
    } );
    return this;
};
EventProxy.prototype.unbind方法:
/**
原版说明:
 * @description Remove one or many callbacks. If `callback` is null, removes all
 * callbacks for the event. If `ev` is null, removes all bound callbacks
 * for all events.
 * @param {string} eventName Event name.
 * @param {function} callback Callback.
总结一下,这个方法做用是根据传递的参数不一样来清楚 EventProxy._callback 内的回调函数的
可是使用list[i]=null,这里我的感受不妥,由于将数组值设置为null之后数组的长度是不会改变的,建议改成:
list.splice(i,1);完全删除这个数组元素
 */
EventProxy.prototype.unbind = EventProxy.prototype.removeListener = function(ev, callback) {
    var calls;
    if (!ev) {      //若是 eventName不存在,则清空EventProxy._callback对象
        this._callbacks = {};
    } else if (calls = this._callbacks) {   //若是 EventProxy ._callback存在,这个判断感受有点多余,多是为了严谨
        if (!callback) {
            calls[ev] = [];   //若是不传回调函数,则直接清空EventProxy._callback对象中的eventName属性
        } 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;   //判断传递过来的callback在EventProxy._callback对象的eventName属性中是否存在,若是存在则把改callback清空
                break;
            }
        }
        }
    }
    return this;
};
接下来就是核心方法了,
EventProxy.prototype.bind方法
/**
原版说明:
 * @description Bind an event, specified by a string name, `ev`, to a `callback` function.
 * Passing `"all"` will bind the callback to all events fired.
 * @param {string} eventName Event name.
 * @param {function} callback Callback.
 */
EventProxy.prototype.bind = EventProxy.prototype.on = EventProxy.prototype.addListener = function(ev, callback) {
    this._callbacks = this._callbacks || {}; 
    var list  = this._callbacks[ev] || (this._callbacks[ev] = []);  
//给list赋值,若是EventProxy._callbacks[ev]不存在,则将EventProxy._callbacks[ev]设置为数组
    list.push(callback);  //将回调函数塞入EventProxy._callbacks[ev]
    return this;
};
 
又是改变函数做用域,又是方法互相调来调去好不混乱,无论你有没有晕,反正我是晕了。休息一会,让咱们简单看一下本例中当执行了proxy.assign("v1", "v2", "v3", add);这个方法后,proxy对象改变了那些呢?proxy是EventProxy的一个实例,因此上面代码中出现的EventProxy均可以用proxy来代替了。
首先是proxy._callbacks,他里面会存放为:
{
   all:(all函数), 
   v1:[(上面紫色的函数)], 
   v2:[(上面紫色的函数)],
   v3:[(上面紫色的函数)]
}
 
午休片刻,我继续上路。
当异步结果回来之后,EventProxy当地又作了什么呢?咱们来看代码:
 clinet1.get("key1", function (err, data) {
        //do something
        proxy.trigger("v1", data);    //异步操做返回之后,调用proxy.trigger传入第一个标识v1和异步结果data;
    });
EventProxy.prototype.trigger方法:
/**
原版说明:
 * @description Trigger an event, firing all bound callbacks. Callbacks are passed the
 * same arguments as `trigger` is, apart from the event name.
 * Listening for `"all"` passes the true event name as the first argument.
 * @param {string} eventName Event name.
 * @param {mix} data Pass in data. 
 */
EventProxy.prototype.emit = EventProxy.prototype.fire = EventProxy.prototype.trigger = function(eventName, data, data2) {
    var list, calls, ev, callback, args, i, l;
    var both = 2;    //好奇怪的2!咱们继续看下去!
    if (!(calls = this._callbacks)) return this;  //又见容错代码
    while (both--) {    //both是2,这里循环执行2次
        ev = both ? eventName : 'all';   //第一次ev是eventname,第二次是all,上行 while (both--) 至关于while (both) 而后执行 both = both -1
        if (list = calls[ev]) {
            for (i = 0, l = list.length; i < l; i++) {
                if (!(callback = list[i])) {
                    list.splice(i, 1); i--; l--;  //这个也属于容错
                } else {
                    args = both ? Array.prototype.slice.call(arguments, 1) : arguments; 
//第一次args是data1-n,第二次是整个参数数组即:eventName,data1-n
                    callback.apply(this, args); 
//第一次依次执行EventProxy._callbacks[eventName]属性数组下的回调,本例只有一个回调执行一次
//第二次依次执行EventProxy._callbacks.all属性数组下的回调,本例只有一个回调执行一次
                }
            }
        }
    }
    return this;
};
/*
看完上面代码是否是有点懂了
没错就像你所想象的那样,咱们回头看以前绿色的bind函数和all函数
第一次执行while循环将:
一、异步结果保存在proxy._fired中,而后将times+1,标志flag,这个flag可能本意是为了告知用户返回了哪一个异步请求的结果,可是做者并无彻底利用起来。其实能够做为 EventProxy.prototype.trigger的一个返回值给用户。
二、执行unbind方法,将 proxy._callbacks[eventname]下的本回调函数解绑。
第二次执行while循环将:
一、执行上面粗绿色的all函数,根据times和length判断是否已经所有返回。
*/
基本源代码的分析结束了,咱们来总结一下把:
理论上来讲实例化一次 EventProxy 能够屡次享用并不会形成冲突,由于其times和length是放在函数内的局部变量,各个方法之间互不干涉。可是也有局限性,就是eventname标识不能取同名,不然就会冲突了。
EventProxy 的实现很巧妙,虽然代码互相调来调去让人头晕,其实能够稍微改写一下代码,让EventProxy 只有一个入口调用各个功能模块,功能模块之间不要互相调用,充分利用return 返回值,这样代码阅读起来会更加容易。
相关文章
相关标签/搜索