Nodejs EventProxy模块代码的研究和心得

Nodejs EventProxy模块代码的研究和心得

   如果大家研究过node_club源码,想必不会对EventProxy模块感到陌生吧!该模块是由@朴灵所创作出来的,该模块有如下几大特点:

  • 利用事件机制解耦复杂业务逻辑
  • 移除被广为诟病的深度callback嵌套问题
  • 将串行等待变成并行等待,提升多异步场景下的执行效率
  • 无平台依赖,适合前后端,能用于浏览器和Node.js

想要获取eventproxy源码的童鞋们可以通过命令npm install eventproxy或者直接去github网站获取,网址为:[enter link description here][1]

[1]: https://github.com/JacksonTian/eventproxy 那里面有较为详细的介绍,好了,接下来我们步入正题,进行对eventproxy模块源码的探究之旅吧!

由于eventproxy模块中的其他方法都相对来说比较简单,这里,我就不再多费笔墨来解读呢,主要针对其中的_assign方法进行分析,它也是eventproxy的核心所在吧!

var _assign = function (eventname1, eventname2, cb, once){...}

很多内置的函数都使用到了该函数,比如:EventProxy.prototype.all和EventProxy.prototype.tail等方法。好了,接下来我们将其源码剪切下来,其中有对关键代码的详细注释,相信大家一看就很清楚呢!

	var _assign = function (eventname1, eventname2, cb, once) {
    var proxy = this, length, index = 0, argsLength = arguments.length,
        bind, _all,
        callback, events, isOnce, times = 0, flag = {};
// Check the arguments length.
if (argsLength < 3) {
	//由于参数列表的最后两项分别是回调函数
	//和是否只进行一次监听的标识位,因此加上要监听一事件,
	//所以参数列表的长度至少是3位或者3位以上,否则直接返回。
    return this;
}

//获取所有要进行监听的事件名
events = Array.prototype.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;
//bind函数是关键,主要用于各个需要监听的事件绑定到一个指定函数里,主要通过
//method指定的方法进行事件的绑定(once或者bind函数),同时在function(data){...}
//函数里对将传递过来的参数记录下来,以便之后传递给回调函数,并且在这个函数里地相应事件的触发
//进行计数,以便在所有监听的事件都触发后,对callback函数进行回调,见下文
bind = function (key) {
    var method = isOnce ? "once" : "bind";
	//proxy[method]事实就是指eventproxy模块上下文的bind和once事件,进行相应的绑定操作
    proxy[method](key, function (data) {
        proxy._fired[key] = proxy._fired[key] || {};
		//对相应事件传递过来的实参进行记录
        proxy._fired[key].data = data;
        if (!flag[key]) {
            flag[key] = true;
			//times用于对触发相应事件时完成对其的计数功能
            times++;
        }
    });
};

for (index = 0; index < length; index++) {
	//依次对监听的事件进行绑定,使用上面的bind函数(注意不是上下文的bind函数)
    bind(events[index]);
}

_all = function () {
    if (times < length) {
		//这里是重点,作用是为了判断是否所有的监听事件都已经完成了触发动作(可以通过emit事件)
		//,如果还有未触发的事件,则跳出当前函数,也就放弃了对回调函数的调用过程,理解这点对
		//assign函数的原理也就差不多理清楚了
        return;
    }
    var data = [];
    for (index = 0; index < length; index++) {
		//获取所有监听事件所传递过来的实参,以便给回调函数使用
        data.push(proxy._fired[events[index]].data);
    }
    if (isOnce) {
		//当isOnce为true时,则将all事件进行去除绑定,那么回调函数也只会在
		//所有监听事件触发完成后被调用一次,此后便不会再进行回调。
		//相反,如果当isOnce为false时,则在所有监听事件触发完成之后的时间里,
		//只要触发任意的监听函数都会对回调函数进行回调调用。这也就assign和assignAll的本质差异!!!
        proxy.unbind("all", _all);
    }
	//对回调函数进行回调(需要理解javascript的apply或者cal方法的使用)
    callback.apply(null, data);
};
//对_all事件进行绑定,以便能够正确对回调函数进行回调
proxy.bind("all", _all);

};

以上便是对_assign方法的详细说明,注释已经写得比较清楚呢。如果对上述方法的原理理解透彻呢,想必会对看懂node_club源码有所帮助的。顺便提醒一个,上述函数中涉及到了javascript的函数闭包的内容,比如为什么_assign函数中的局部变量times能够一起存活下去?这里大家就需要对函数闭包的知识有所了解才能弄懂了,在这里只是顺带提一下,应该说闭包的内容还是比较复杂的,三言两语也说不清,不懂的就直接去找相关资料进行学习吧。如果有机会,自己也会把自己对对闭包的理解写出来,供大家评阅!!!上述有什么表述不清或者不准确的地方欢迎大家的随时拍砖!!!共同学习!!!


16 回复

Nodejs EventProxy模块代码的研究和心得

引言

如果你研究过 node_club 源码,相信你不会对 EventProxy 模块感到陌生。该模块是由 @朴灵 所创建,其主要特点包括:

  • 利用事件机制解耦复杂业务逻辑
  • 移除被广为诟病的深度 callback 嵌套问题
  • 将串行等待变成并行等待,提升多异步场景下的执行效率
  • 无平台依赖,适合前后端,能用于浏览器和 Node.js

你可以通过 npm install eventproxy 或者访问 GitHub 获取源码。

探究 EventProxy 模块源码

在这篇文章中,我将主要分析 EventProxy 的核心方法 _assign。这个方法是整个模块的关键所在,涉及到事件绑定、回调处理等多个方面。

_assign 方法详解

以下是 _assign 方法的源码及注释:

var _assign = function (eventname1, eventname2, cb, once) {
    var proxy = this, length, index = 0, argsLength = arguments.length,
        bind, _all,
        callback, events, isOnce, times = 0, flag = {};

    // 检查参数数量
    if (argsLength < 3) {
        return this;
    }

    // 获取所有要监听的事件名
    events = Array.prototype.slice.apply(arguments, [0, argsLength - 2]);
    // 获取回调函数
    callback = arguments[argsLength - 2];
    // 获取监听标识位
    isOnce = arguments[argsLength - 1];

    // 检查回调函数类型
    if (typeof callback !== "function") {
        return this;
    }

    length = events.length;

    // 绑定事件的函数
    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++;
            }
        });
    };

    // 对每个事件进行绑定
    for (index = 0; index < length; index++) {
        bind(events[index]);
    }

    // 处理所有事件触发后的回调
    _all = function () {
        if (times < length) {
            return;
        }
        var data = [];
        for (index = 0; index < length; index++) {
            data.push(proxy._fired[events[index]].data);
        }
        if (isOnce) {
            proxy.unbind("all", _all);
        }
        callback.apply(null, data);
    };

    // 绑定 all 事件以处理所有事件触发后的回调
    proxy.bind("all", _all);
};

代码解析

  1. 参数检查:首先检查传入的参数数量,确保至少包含三个参数:事件名、回调函数以及监听标识位。
  2. 事件绑定:遍历所有事件名,对每个事件使用 oncebind 方法进行绑定。绑定的回调函数会记录事件数据并增加计数器 times
  3. 回调处理:定义 _all 函数,在所有事件触发后调用。如果设置了 isOnce,则在第一次调用后解除绑定;否则,每次事件触发后都会调用回调函数。
  4. 事件监听:最后,绑定 all 事件以确保所有事件触发后的回调能够正确执行。

总结

通过深入分析 _assign 方法,我们可以更好地理解 EventProxy 模块如何通过事件机制解耦复杂逻辑,避免深度回调嵌套,并提高异步处理效率。理解这些细节有助于我们在实际项目中更有效地应用 EventProxy。希望本文对你有所帮助,如果有任何疑问或建议,欢迎随时交流!


貌似相对 step没啥特别的优势呢?

step?不太了解您所谓的step概念,是否可以说得具体些?至于eventproxy本身就是一个轻量级的异步/基于事件的的模块,至于本身所具有的特点已经在文章中有所提及呢。当然自己也完全可以参考本思路,自己开发出一个基于异步/基于事件的模块出来!

谢谢分享,看完eventproxy源码之后,再看nodeclue就简单易懂了。

虽然说eventproxy可以用在前端,不过好像也不太适合前端用。

共同进步!!!

今天才看到这个帖子。太感谢楼主了。

当初看step的时候,还不支持并行。

可能前端的应用场景不太相同。但是如果用模块和事件较多的话,它就会体现出价值的。

要用Jscex哇!

楼上是老赵

我用的时候出现了这个问题:Error: db object already connecting, open cannot be called multiple times 望指教~ 参考他的readMe的例子

var ep = EventProxy.create("template", "data", "l10n", function (template, data, l10n) {
  _.template(template, data, l10n);
});

$.get(“template”, function (template) { // something ep.emit(“template”, template); }); $.get(“data”, function (data) { // something ep.emit(“data”, data); }); $.get(“l10n”, function (l10n) { // something ep.emit(“l10n”, l10n); });

似乎是因为我的get方法里面用了mongodb.open()这样的方法造成的不能多次打开数据库问题 我的代码:

var render = function (user, blogs) {
        res.render("user", {title:user.name, blogs:blogs});

    }
    EventProxy.create("user", "blogs", render);
    User.get(req.params.user, function (err, user) {
        if (!user) {
            req.flash("error", "用户不存在");
            return res.redirect("/");
        }
        proxy.emit("user", user);
    });
    Blog.get(req.params.user, function (err, blogs) {
        if (err) {
            req.flash("error", err);
            return res.redirect("/");
        }
        proxy.emit("blogs", blogs);
    });

Blog.get 代码:

Blog.get = function get(username, callback) {
        mongodb.open(function (err, db) {
            if (err) {
                return callback(err);
            }
            // 讀取 blogs 集合
            db.collection('blogs', function (err, collection) {
                //处理
            });
        });
    };

User.get代码

User.get = function (username, callback) {
    db.open(function (err, db) {
        if (err) {
            return callback(err);
        }
        db.collection("users", function (err, collection) {
            //处理
        });
    });
}

这个有什么好的解决方法吗?

我想问一下关于代码最前面的这段是什么意思,能详细说说么……

;
(function (name, definition) {
  // this is considered "safe":
  var hasDefine = typeof define === 'function',
    // hasDefine = typeof define === 'function',
    hasExports = typeof module !== 'undefined' && module.exports;

if (hasDefine) { // AMD Module or CMD Module define(definition); } else if (hasExports) { // Node.js Module module.exports = definition(); } else { // Assign to common namespaces or simply the global object (window) this[name] = definition(); } })

比较想问下这个模块相对 async 有什么优势?async 还不需要自己去关注事件点,用起来更加省心~

之前我用的也是这个模块,但对个人来说有些不适,后来自己就开发了一套,部分内容比起 eventproxy 要好很多。

eventproxy

需要先创建,然后再 emit 触发,不是很灵活。从上面的例子可以看出来。

howdo

而 howdo 却不一样。 howdo.task().task().task() 链式创建有顺序的任务。 这些任务怎么执行?有2个 API ,一个是串行(follow),一个是并行(together)。 项目地址:https://www.npmjs.org/package/howdo 同时,howdo 也支持浏览器端的异步流程控制。

在 Node.js 中,EventProxy 模块通过事件机制解耦复杂业务逻辑,移除了深度回调嵌套问题,并将串行等待变为并行等待,从而提升多异步场景下的执行效率。该模块可以在 npm 上安装或直接从 GitHub 获取。

下面是 EventProxy 模块中的 _assign 方法的简化版实现,帮助理解其核心原理:

const EventProxy = require('eventproxy');

const ep = new EventProxy();

// 定义一个简单的异步函数
const asyncFunction = (eventName, delay, data) => {
    setTimeout(() => {
        ep.emit(eventName, data);
    }, delay);
};

// 使用_eventProxy的assign方法
ep.assign(['event1', 'event2'], (data1, data2) => {
    console.log(`Data received: ${data1}, ${data2}`);
});

// 发射事件
asyncFunction('event1', 1000, 'Hello');
asyncFunction('event2', 500, 'World');

在这段代码中,我们创建了一个 EventProxy 实例,并定义了一个简单的异步函数 asyncFunction 来模拟异步操作。然后,我们使用 ep.assign 方法将两个事件 event1event2 绑定在一起,并在所有事件都触发后执行回调函数。

通过这种方式,我们可以轻松处理多个异步操作的结果,而无需担心回调地狱的问题。EventProxy 模块非常适合处理复杂的异步操作,并且可以提高代码的可读性和维护性。

回到顶部