异步编程解决方案之事件发布/订阅模式

时间监听模式是广泛用于异步编程的模式,是回调函数的事件化,又称不发订阅模式。

nodejs的events模块就是发布订阅模式的一个简单实现,不存在preventDefault,stopPropagation,stopImmediatePropagation,等控制事件传递的方法。

它具有addListner/on(),once(),removeListner(),removeAllLisetner()和emit等基础监听事件方法。

事件发布/订阅十分简单,如下:

//订阅
emitter.on('event1', function (message) {
  console.log(message)
})
//发布
emmiter.emit('event1', 'this is a message');

可以看到,事件订阅就是一个高阶函数的应用。

 

事件侦听器就是一个钩子函数hook

const options = {
    host: 'www.abc.com',
    port: '80',
    path: '/get',
    method: 'GED'
}

let req = http.request(options, function (res) {
    console.log('STATUS:' + res.statusCode);
    console.log('HEADERS:', JSON.stringify(res.headers));
    res.setEncoding('utf8');
    res.on('data', function (chunck) {
        console.log(chunck);
    });
    res.on('end', function () {
        console.log(' -- end -- ');
    })
    
    req.on('error', function (e) {
        console.log('problem with requrest: ' + e.message);
    })
    req.wirte('data\n');
    req.wirte('data\n');
    req.end();
});

在这段程序内,程序员只需要关注内部的error、data、end这些事件点上即可,至于内部的流程,无需关注。

备注:如果一个事件添加了超过十个侦听器就会被警告,防止CPU和内存暂用过多资源,emitter.setMaxListeners(0)将这个限制去掉;

   EventEmitter对error事件做了特殊对待,如果这个事件没有被捕获,就会引起线程退出。一个健壮的EventEmitter应该对error处理;

 

1、继承EventEmitter类是十分简单的,以下代码是node中Stream对象继承EventEmitter的例子

let events = require('events');
 
function Stream () {
  events.EventEmitter.call(this);
}

util.inherits(Stream, events.EventEmitter);

node核心模块中有近半数继承自EventEmitter

 

2、利用事件队列解决雪崩问题

假如站点刚好启动,这时候缓存中是不存在数据的,而如果访问量巨大,同一个sql发送到数据库中反复查询,会影响服务的整体性。

var select = function (callback) {
  db.select('SQL', function (results) {
    callback(results);
  })  
}

解决方案是添加一个状态锁

var status = 'ready';
var select = function (callback) {
  if (status === 'ready') {
    status === 'pending';
    db.select('SQL', function(results) {
      status = 'ready';
      callback(results);
    })
  }
}

但是这种情况下,多次调用select,只会有一次生效,后续select没有数据服务的,这个时候我们可以引入事件队列:

var proxy = new events.EventEmitter();
var status = 'ready';
var select = function (callback) {
    proxy.once('selected', callback);
    if (status === 'ready') {
        status === 'pending';
        db.select('SQL', function (results) {
            proxy.emit('select', results);
            status = 'ready';
        })
    }
}

这里面我应用了once方法,将请求回调都压在事件队列中,利用他执行一次就将监听器移除的特点,保证他每一个回调都执行一次。

 

posted @ 2018-07-26 21:40  秦孝公嬴渠梁  阅读(247)  评论(0编辑  收藏  举报