Null's Blog

nodejs笔记2--node中的事件通知机制

填坑:

1.nodejs笔记1–异步API与其局限性

2.nodejs笔记2–node中的事件通知机制

###异步编程的难点
nodejs令异步编程如此风行,这也是异步编程首次大规模出现在业务层面.它借助异步I/O模型及V8高性能引擎,突破单线程的性能瓶颈,让javascript在后端达到使用价值.另一方面,它也统一了前后端javascript的编程模型.对于异步编程带来的新鲜感与不适感,开发者们有着不同程度的感受.接下来,按照@朴灵的《深入浅出nodejs》梳理一下异步编程的难点:

难点1:异常处理

浏览器端通常使用try/catch/final包裹代码块进行异常捕获与处理:

1
2
3
4
5
try{
dosomething();
}catch(e){
docatch();
}

但这对于异步编程而言并不一定实用.异步I/O的实现主要包含两个阶段:提交请求和处理结果.这两个阶段有事件循环的调度,两者彼此并不关联:

1
2
3
var asnyc = function(callback){
process.nextTick(callback);
};

调用async()后,callback被存放起来,直到下一个事件循环(tick)才会取出来执行.尝试对异步方法进行try/catch操作只能捕获当次事件循环内的异常.对callback执行时抛出的异常将无能为力:

1
2
3
4
5
try{
async(callback);
}catch(e){
docatch();
}

另一个错误的示范:

1
2
3
4
5
6
7
8
try{
req.body = JSON.parse(buf,options.reviver);
callback(); //写在这是为了捕获了异常依旧执行回调.但如果callback内存在异常,则callback将被执行两次.
}catch(e){
err.body = buf;
err.status = 400;
callback(err);
}

难点2:函数嵌套过深

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var class=require('./module/class');
export.pageStudent=function(req,res){
var rtnJson={};
class.getStudent('1班',function(error,oneResult){
if(!error&&oneResult){
rtnJson['1班']=oneResult;
class.getStudent('2班',function(error,twoResult){
if(!error&&twoResult){
rtnJson['2班']=twoResult;
class.getStudent('3班',function(error,threeResult){
if(!error&&threeResult){
rtnJson['3班']=threeResult;
//3个班级全部获取完成
res.render('./veiw/pageStudent',{students:rtnJson});
}else{
res.render('./veiw/pageStudent',{students:rtnJson});
}
});
}else{
res.render('./veiw/pageStudent',{students:rtnJson});
}
});
}else{
res.render('./veiw/pageStudent',{students:rtnJson});
}
});
}

某些有代码洁癖的人可能会觉得难以接受.

难点3:阻塞代码

使用setTimeout和setInterval实现程序逻辑上的等待功能,并在此基础事件工作者/生产者队列模型.在没有信号量的语言中这并不十分优雅.

难点4:多线程编程

浏览器端WebWorkers的相关使用,和服务器端的child_process cluster等.初学者很难快速适应.

异步编程的解决方案

事件发布/订阅模式

nodejs 自身提供events模块是发布/订阅模式的一个简单实现,nodejs中也有部分模块继承自它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var EventEmitter = require('events').EventEmitter;
var ee = new EventEmitter();
//定义侦听器1
ee.on('data',function(args){
//doSomething1();
});
//定义侦听器2
ee.on('data',function(args){
//doSomething2();
});
//定义只执行一次的侦听器
ee.once('data',function(args){
//doOnce();
});
ee.emit('data',{});

直接通过emit发射一个事件,上面定义的对应的方法将按定义的顺序依次执行并接收到传递的参数.API很简单,但有些额外的东西也需要有所了解

  1. 如果一个事件添加了超过10个侦听器,将会得到一条警告,但可以使用ee.setMaxListeners(0)去掉这个限制
    2.EventEmitter对象对error事件进行了特殊对待,如果运行期间错误触发了error事件,EventEmitter会检查是否对error事件添加过侦听器,如果添加了,则有这个侦听器处理,如果没添加则会作为异常抛出.如果外部没有捕获这个异常,将会引起线程退出.一个健壮的EventEmitter实例应该对error事件做处理.
    3.EventEmitter可以在一定程度上解耦业务逻辑,比如上篇博文nodejs笔记1–异步API与其局限性中的异常情况,将要处理的数据保存在一个数组内,逐个取出执行后续的消耗大量系统资源的操作,完成后发射事件取出并执行下一个,知道所有业务处理完毕.