Null's Blog

nodejs笔记3--事件通知机制的两个应用场景

填坑:

1.nodejs笔记1–异步API与其局限性
2.nodejs笔记2–nodejs中的事件通知机制
3.nodejs笔记3–事件通知机制的两个应用场景

应用场景

上篇博客介绍了几个EventEmitter的API使用,简单高效,有利于解耦业务逻辑.

通过once()方法解决雪崩问题

在事件订阅/发布模式中,通过once()方法注册的侦听器只会被执行一次,在执行之后会将它与事件的关联移除.实际开发中可以利用这点过滤一些重复性的事件响应.
例如在高访问量和大并发量的情况下,以下直接的写法可能导致数据库无法同时承受,进而影响到网站整体的响应调用:

1
2
3
4
5
var select = function(callback){
db.select("SQL",function(results){
callback(results);
});
}

当然,加入状态锁可以解决并发问题,不过除了第一个请求得到响应外,其他的请求将不会得到相应:

1
2
3
4
5
6
7
8
9
10
var status = 'ready';
var select = function(callback){
if(status === 'ready'){
status = 'panding';
db.select('SQL',function(results){
status = 'ready';
callback(results);
});
}
}

这时候可以引入事件队列:

1
2
3
4
5
6
7
8
9
10
11
var event = require('event');
var ee = new event.EventEmitter();
var select = function(callback){
ee.once('selected',callback); //注册多个只执行一次的侦听器或者可以理解为将这些存入一个队列
if(status === 'ready'){
status = 'pending';
db.select('SQL',function(results){
ee.emit('selected',results); //只查询一次,并将结果发射给之前注册的多个将只执行一次的侦听器
});
}
}

只查询一次,并将结果发射给之前注册的多个将只执行一次的侦听器,借助EventEmitter将结果发射给所有队列,并由EventEmitter帮我们处理后续各种回收问题.当然,之前介绍过,这需要配合setMaxListeners(0)移除侦听器数量限制.

使用EventEmitter解决多异步之间的协同方案

事件发布/订阅模式可以隔离业务逻辑,保持业务逻辑单元的职责单一,一般而言,事件与侦听器的关系是一对多,但在异步编程中,也会出现事件与侦听器是多对一的情况,也就是说一个业务逻辑可能依赖三个通过回调或事件传递的结果.
这里介绍渲染页面需要读取两个数据,在不使用Promise async Q等库的情况下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var count = 0;
var results = {};
var done = function(key,value){
results[key] = value;
count++;
if(count === 3){ //三个依赖均已正常完成
render(); //渲染页面
}
}
db.select('SQL1',function(results){
done('data1',results);
});
db.select('SQL2',function(results){
done('data2',results);
});
User.get(function(err,user)){
done('user',user);
});

如果使用发布/订阅模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var ee = new require('event').EventEmitter();
var after = function(times,callback){
var count = 0;
var results = {};
return function(key,value){
results[key] = value;
count++;
if(count === times){
callback(results);
}
}
}
ee.on('done',after);
db.select('SQL1',function(results){
ee.emit('done',3,results);
});
db.select('SQL2',function(results){
ee.emit('done',3,results);
});
User.get(function(err,user)){
ee.emit('done',3,user);
});

当然,可以根据这个思路封装成通用的组件,这里不再赘述相关的内容.