使用

  1. const { EventEmitter } = require('events');
  2. const emitter = new EventEmitter();
  3. emitter.on("起床", function(time) {
  4. console.log(`早上 ${time} 开始起床,新的一天加油!`);
  5. });
  6. emitter.emit("起床", "6:00");

EventEmitter 解决高并发下雪崩问题

利用 once 方法将所有请求的回调都压入事件队列中,对于相同的文件名称查询保证在同一个查询开始到结束的过程中永远只有一次,如果是 DB 查询也避免了重复数据带来的数据库查询开销

  1. const events = require('events');
  2. const emitter = new events.EventEmitter();
  3. const fs = require('fs');
  4. const status = {};
  5. const select = function(file, filename, cb) {
  6. emitter.once(file, cb);
  7. if (status[file] === undefined) {
  8. status[file] = 'ready'; // 不存在设置默认值
  9. }
  10. if (status[file] === 'ready') {
  11. status[file] = 'pending';
  12. fs.readFile(file, function(err, result) {
  13. console.log(filename);
  14. emitter.emit(file, err, result.toString());
  15. status[file] = 'ready';
  16. setTimeout(function() {
  17. delete status[file];
  18. }, 1000);
  19. });
  20. }
  21. }
  22. for (let i=1; i<=11; i++) {
  23. if (i % 2 === 0) {
  24. select(`/tmp/a.txt`, 'a 文件', function(err, result) {
  25. console.log('err: ', err, 'result: ', result);
  26. });
  27. } else {
  28. select(`/tmp/b.txt`, 'b 文件', function(err, result) {
  29. console.log('err: ', err, 'result: ', result);
  30. });
  31. }
  32. }

同步还是异步

EventEmitter 会按照监听器注册的顺序同步地调用所有监听器。 所以必须确保事件的排序正确,且避免竞态条件。

错误处理

最佳实践:为 ‘error’ 事件注册监听器,避免抛出一个错误时没有人为处理,可能造成的结果是进程自动退出

  1. const events = require('events');
  2. const emitter = new events.EventEmitter();
  3. emitter.on('error', function(err) {
  4. console.error(err);
  5. })
  6. emitter.emit('error', new Error('This is a error'));
  7. console.log('test');

发布/订阅者模块

Publish.png

实现

基本结构

  1. function EventEmitter() {
  2. // 私有属性,保存订阅方法
  3. this._events = {};
  4. // 已经绑定的事件数
  5. this._eventsCount = 0;
  6. }
  7. // 默认设置最大监听数
  8. EventEmitter.defaultMaxListeners = 10;
  9. module.exports = EventEmitter;

on 方法

  1. EventEmitter.prototype.on = function(type, listener, flag) {
  2. // 保证存在实例属性
  3. if (!this._events) this._events = Object.create(null);
  4. if (this._events[type]) {
  5. if (flag) {
  6. this._events[type].unshift(listener);
  7. } else {
  8. this._events[type].push(listener);
  9. }
  10. } else {
  11. this._events[type] = [listener];
  12. ++ this._eventsCount;
  13. }
  14. // 'newListener' 是 node 的 EventEmitter 模块自带的特殊事件
  15. // 该事件在添加新事件监听器的时候触发
  16. if (type !== 'newListener') {
  17. this.emit('newListener', type, listener);
  18. }
  19. // 为了可以链式调用,返回了EventEmitter模块的实例化本身
  20. return this;
  21. };

emit 方法

  1. EventEmitter.prototype.emit = function(type, ...args) {
  2. if (this._events[type]) {
  3. this._events[type].forEach(fn => fn.call(this, ...args));
  4. }
  5. };

once

通过在原本的函数外再包一层函数来达到增强的作用

  1. EventEmitter.prototype.once = function(type, listener) {
  2. let _this = this;
  3. function only() {
  4. listener();
  5. _this.removeListener(type, only);
  6. }
  7. // origin 保存原回调的引用,用于 remove 时的判断
  8. only.origin = listener;
  9. this.on(type, only);
  10. };

off

  1. EventEmitter.prototype.off =
  2. EventEmitter.prototype.removeListener = function (type, listener) {
  3. if (this._events[type]) {
  4. //过滤掉退订的方法,从数组中移除
  5. this._events[type] =
  6. this._events[type].filter(fn => {
  7. return fn !== listener && fn.origin !== listener
  8. });
  9. }
  10. };

参考资料

  1. 【源码解析】一文彻底搞懂 Events 模块
  2. 循序渐进教你实现一个完整的node的EventEmitter模块
  3. Node.js Events 模块