需求场景:某一业务逻辑要求加载三个相互之间不关联的资源  一个磁盘上的文件,读取数据库的数据,并且取得某个远程API的返回数据。而我们的处理要等待这三个资源全部加载完毕后才进行处理。

不同于传统的编程语言,在同步编程模型下这样的代码很好的,三行加截,第四行判断,然后就可以开始写处理逻辑了。花费的时间是 a + b + c

1
2
3
4
5
var file = File.readAll("d:/test.txt");
var data = Data.selectAll("SELECT * FROM ...");
var list = Api.getList("http://www.api-example.com/api");
if (file && data && list)
  // to do process;

而Node.js 的I/O库都是异步设计,那么我们可能会这样写:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fs.readFile("d:/test.txt", "utf-8", function(err, content) {
  if (err) return handleErr(err);
  db.query("SELECT * FROM ...", function(err, result) {
    if (err) return handleErr(err);
    api.getList("http://www.api-example.com/api", function(err, content) {
      if (err) return handleErr(err);
      // to do process;
    }
  }
}

这样代码虽然可以正常工作,但花费的时间跟同步代码是一样的,并且嵌套太多,代码维护会很不方便。我们加载的三个资源中间并没有前后依赖的关系,因此更好的处理方法是同时对这三个资源进行加载,在 最后一个资源加载成功后执行我们的处理操作。这样一来,花费的时间便成了:max(a, b, c)  利用 EventProxy 可以很简单地完成这样的任务:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
var ep = new EventProxy();
ep.all('file', 'data', 'list' // 声明所有需要加载的资源名称
  , function(file, data, list) { // 处理函数,参数列表与资源名称相对应。
    // to do process
  }
);

ep.fail(handleErr);
fs.readFile('file', ep.done('file'));
db.query('SELECT * FROM ..', ep.done('data'));
api.getList("http://www.api-example.com/api", ep.done("list"));

这样一样整个程序结构清晰很多,也不必每次对 err 进行判断处理。

EventProxy 的实现得益于 Node.js 的异步库标准化处理方式, Node.js 的异步IO库都会接收一个 callback 函数作为回调,根据约定, callback 函数第一个参数是 err,第二个参数就是加载完成的结果,  ep.done 是一个偏函数,它会返回一个函数,这个函数帮你判断是否有错误发生,如果有的话则触发 fail 事件,那么我们的 handleErr 函数就会被执行,如果所有的资源都成功加载则触发 all 事件,这时候我们在 all 中传入的回调函就会被执行, 同时相应的资源也会被作为参数传入到我们的函数中。