Emitter 学习笔记

Author Avatar
Teeeemoji 10月 21, 2017
  • 在其它设备中阅读本文章

Emitter 学习笔记

学习感想

最好的学习资料是官方文档, 由于是英语的, 可能造成心里压力, 阅读不下去(我就是).

我个人是先从一篇 emitter 的博客对 Emitter 稍有了解再去看官方文档的, 果然这个模块真是小还简单.

如果这篇文章有幸被你参考, 倍感荣幸!

概要介绍

中文翻译 不明所以, 大概是事件分发的意思.

  • emit: 散发,发射
  • emitter: 发射器

Nodejs 中很多 API 或者内部类是基于异步调用的事件机制的, 在 nodejs 中,所有能 emit 事件的类都继承自 Emitter

Emitter 是 events模块导出的唯一的类, 通常作为接口被其他类继承(或可以直接实例化使用,写 demo 的时候可以这么做)

一个继承了 Emitter 的类, 就被注册事件, 也有了响应事件调用回调函数的能力

当 Emitter 类的某个事件被触发了, 这个事件绑定的所有回调函数, 将会按照 绑定顺序 同步执行.

通过 demo 一步一步了解 Emitter

注意: 所有的 demo 都是 ES6 的写法

demo1: API - on(绑定事件) & emit(触发事件)

通过 Emitter.on()方法绑定事件, 绑定的回调方法, 可以是匿名函数, 也可以是 ES6 的箭头函数

注意: Emitter.on() 方法有个别名, 即 Emitter.addListener() 方法

emitter.on(eventName, listener)

通过 Emitter.emit()方法触发事件,可以传参数的

emitter.emit(eventName[, ...args])

下面是一个可以直接执行的 demo

const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
myEmitter.on('event', () => {
//myEmitter.addListener(‘event', () => {
    console.log('an event occurred!');
});
myEmitter.emit('event');

输出结果:

an event occurred!

demo2: 通过 emit 向事件回调方法传入参数

通过 on() 绑定的回调方法是可以带有参数的,这些参数通过 emit() 方法传进回调函数中

下面是一个可以直接执行的 demo

const EventEmitter = require('events');
const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
myEmitter.on('event', function(a, b) {
    console.log(a, b, this);
});
myEmitter.emit('event', 'a', 'b');

输出结果:

a b MyEmitter {
  domain: null,
  _events: { event: [Function] },
  _eventsCount: 1,
  _maxListeners: undefined }

demo3: 事件回调方法的执行顺序

一个事件可以绑定多个回调方法, 当这个事件被触发时, 所有的回调方法会按照它们绑定的顺序依次同步执行.

注意: 回调事件中可以执行异步操作

下面是一个可以直接执行的 demo

const EventsEmitter = require('events');
class MyEmitter extends EventsEmitter {}
let myEmitter = new MyEmitter();
const cb1 = () => setTimeout(() => {
    console.log('cb1')
}, 100);
// const cb1 = () => console.log('cb1');
const cb2 = () => console.log('cb2');
const cb3 = () => console.log('cb3');
const cb4 = () => console.log('cb4');
myEmitter.on('me',cb1);
myEmitter.on('me',cb2);
myEmitter.on('me',cb3);
myEmitter.on('me',cb4);
myEmitter.emit('me');

输出结果:

cb2
cb3
cb4
cb1

demo4: API - once(只会被调用一次的回调方法)

通过 on() 绑定的回调方法, 无论被 emit() 触发多少次, 都会被执行.

通过 once() 绑定的回调方法, 在回调方法绑定后第一次触发才会被执行.(实际上执行后该回调方法, 就在该事件的回调列表中去除了).

注意: once() 指的是该回调方法只会被触发一次, 并非该事件只会被触发一次

下面是一个可以直接执行的 demo

const EventsEmitter = require('events');
class MyEmitter extends EventsEmitter {}
let myEmitter = new MyEmitter();
const cb1 = () => console.log('cb1');
const cb2 = () => console.log('cb2');
const cb3 = () => console.log('cb3');
myEmitter.once('me',cb1);
myEmitter.once('me',cb2);
myEmitter.on('me',cb3);
// 执行两次 me 事件
myEmitter.emit('me');
myEmitter.emit('me');

输出结果:

cb1
cb2
cb3
cb3

demo5: API - prependListener(在队首添加回调方法)

绑定事件的方法,用法同 on().

然而通过 on 绑定的回调方法, 会 push 进回调方法队列, 后绑定后执行.

通过 prependListener 绑定的回调方法, 则是会 unshift 进回调方法队列, 后绑定先执行.

下面是一个可以直接执行的 demo

const EventsEmitter = require('events');
class MyEmitter extends EventsEmitter {
}
let myEmitter = new MyEmitter();
const cb1 = () => console.log('cb1');
const cb2 = () => console.log('cb2');
const cb3 = () => console.log('cb3');
const cb4 = () => console.log('cb4');
myEmitter.on('me', cb1);
myEmitter.on('me', cb2);
myEmitter.on('me', cb3);
myEmitter.prependListener('me', cb4);
myEmitter.emit('me');

输出结果:

cb4
cb1
cb2
cb3

demo6: API - prependOnceListener(队首绑定一次性回调)

不赘述, 参考 once() & prependListener()
没有什么特殊的

demo7: API - setMaxListeners & getMaxListeners (get & set 可绑定回调的上限)

默认情况下,每个事件的可绑定回调方法上限是10个, 当绑定第11个回调方法的时候, 程序会抛出 warning.虽然程序会抛出 warning, 但是当事件被触发的时候, 所有的回调方法都会正常执行.

setMaxListeners 方法是设置 Emitter 可绑定回调方法的上限(不区分每一个事件的可绑定回调方法上限, 可增可减).

getMaxListeners 方法则是获取某个 Emitter 的可绑定回调方法上限.

下面是一个可以直接执行的 demo

const EventsEmitter = require('events');
class MyEmitter extends EventsEmitter {
}
let myEmitter = new MyEmitter();
// 没有下面这一行, 程序会包 warning 哟
// myEmitter.setMaxListeners(20);
const cb1 = () => console.log('cb1');
const cb2 = () => console.log('cb2');
const cb3 = () => console.log('cb3');
const cb4 = () => console.log('cb4');
const cb5 = () => console.log('cb5');
const cb6 = () => console.log('cb6');
const cb7 = () => console.log('cb7');
const cb8 = () => console.log('cb8');
const cb9 = () => console.log('cb9');
const cb10 = () => console.log('cb10');
const cb11 = () => console.log('cb11');
const cb12 = () => console.log('cb12');
const cb13 = () => console.log('cb13');
const cb14 = () => console.log('cb14');
const cb15 = () => console.log('cb15');
myEmitter.on('me', cb1);
myEmitter.on('me', cb2);
myEmitter.on('me', cb3);
myEmitter.on('me', cb4);
myEmitter.on('me', cb5);
myEmitter.on('me', cb6);
myEmitter.on('me', cb7);
myEmitter.on('me', cb8);
myEmitter.on('me', cb9);
myEmitter.on('me', cb10);
myEmitter.on('me', cb11);
myEmitter.on('me', cb12);
myEmitter.on('me', cb13);
myEmitter.on('me', cb14);
myEmitter.on('me', cb15);
myEmitter.emit('me');
console.log('max listeners = ' + myEmitter.getMaxListeners());

输出结果:

cb1
cb2
cb3
cb4
cb5
cb6
cb7
cb8
cb9
cb10
cb11
cb12
cb13
cb14
cb15
max listeners = 10
(node:14318) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 me listeners added. Use emitter.setMaxListeners() to increase limit

demo8: API - listenerCount(某个事件回调列表的 length)

这个方法返回某个事件所绑定的 回调方法的个数.右有两种写法, 结果是一致的.(这些 api 就是这么简单)

下面是一个可以直接执行的 demo

const EventsEmitter = require('events');
class MyEmitter extends EventsEmitter {}
let myEmitter = new MyEmitter();
myEmitter.on('event', () => {});
myEmitter.on('event', () => {});
console.log(EventsEmitter.listenerCount(myEmitter, 'event'));
console.log(myEmitter.listenerCount('event'));

输出结果:

2
2

demo9: API - listeners(返回事件回调方法列表)

获取某个事件的所有回调方法的列表, 顺序是回调方法的执行顺序.

注: 想看到结果, 要把 listeners() 的返回值打印一下

下面是一个可以直接执行的 demo

const EventsEmitter = require('events');
class MyEmitter extends EventsEmitter {
}
let myEmitter = new MyEmitter();
const cb1 = () => console.log('cb1');
const cb2 = () => console.log('cb2');
const cb3 = () => console.log('cb3');
const cb4 = () => console.log('cb4');
myEmitter.on('me', cb1);
myEmitter.on('me', cb2);
myEmitter.on('me', cb3);
myEmitter.on('me', cb4);
console.log('listeners list = ');
console.log(myEmitter.listeners('me'));

输出结果:

listeners list = 
[ [Function: cb1],
  [Function: cb2],
  [Function: cb3],
  [Function: cb4]]

demo10: removeAllListeners(移除全部事件回调)

无参数->移除所有的事件

有参数(eventName)->移除某一个事件(eventName)的所有回调方法

下面是一个可以直接执行的 demo

const EventsEmitter = require('events');
class MyEmitter extends EventsEmitter {
}
let myEmitter = new MyEmitter();
const cb1 = () => console.log('cb1');
const cb2 = () => console.log('cb2');
const cb3 = () => console.log('cb3');
myEmitter.on('me', cb1);
myEmitter.on('me', cb2);
myEmitter.on('my', cb3);
console.log('nothing removed:')
myEmitter.emit('me');
myEmitter.emit('my');
console.log('remove me:');
myEmitter.removeAllListeners('me');
myEmitter.emit('me');
myEmitter.emit('my');
console.log('remove all:');
myEmitter.removeAllListeners();
myEmitter.emit('me');
myEmitter.emit('my');

输出结果:

nothing removed:
cb1
cb2
cb3
remove me:
cb3
remove all:

demo11: API - removeListener(移除单个事件回调)

移除某一个事件的某一个回调方法,入参 [eventName,callback] 一个都不能少, 否则报错

如果某个事件,绑定了3个相同的回调方法 cb3,那么 removeListener 只会移除最后绑定的那个 cb3

下面是一个可以直接执行的 demo

const EventsEmitter = require('events');
class MyEmitter extends EventsEmitter {
}
let myEmitter = new MyEmitter();
const cb1 = () => console.log('cb1');
const cb2 = () => console.log('cb2');
const cb3 = () => console.log('cb3');
myEmitter.on('me', cb1);
myEmitter.on('me', cb2);
myEmitter.on('me', cb3);
myEmitter.on('me', cb2);
myEmitter.on('me', cb3);
console.log('before remove:');
myEmitter.emit('me');
console.log('after remove cb2:');
myEmitter.removeListener('me',cb2);
myEmitter.emit('me');

输出结果:

before remove:
cb1
cb2
cb3
cb2
cb3
after remove cb2:
cb1
cb2
cb3
cb3

demo12: 特殊事件 - error 事件

Emitter 支持一个特殊的事件 -error, 当这个事件被触发的时候, 如果找不到处理的回调方法, 会导致程序崩溃.

下面是一个可以直接执行的 demo

const EventsEmitter = require('events');
class MyEmitter extends EventsEmitter {}
let myEmitter = new MyEmitter();
myEmitter.emit('error', new Error('whoops!'));

输出结果:
程序非正常退出

events.js:163
      throw er; // Unhandled 'error' event
      ^

Error: whoops!
    at Object.<anonymous> (/Users/zhangtingcen/Documents/my-repo/learning/index.js:60:25)
    at Module._compile (module.js:571:32)
    at Object.Module._extensions..js (module.js:580:10)
    at Module.load (module.js:488:32)
    at tryModuleLoad (module.js:447:12)
    at Function.Module._load (module.js:439:3)
    at Module.runMain (module.js:605:10)
    at run (bootstrap_node.js:423:7)
    at startup (bootstrap_node.js:147:9)
    at bootstrap_node.js:538:3

demo13: 特殊事件 - process.uncaughtException

因此我们需要一种手段, 来防止(上面)因为没有正确处理 error 事件导致程序直接崩溃.

可以通过给 precess 绑定 uncaughtException 事件, 当 Emitter 类的 error 事件被触发且没有 error 事件的回调方法时, process 的 uncaughtException 事件回调将会被触发.程序也不会崩溃.

下面是一个可以直接执行的 demo

const EventsEmitter = require('events');
class MyEmitter extends EventsEmitter {}
let myEmitter = new MyEmitter();
process.on('uncaughtException', (err) => {
    console.error('whoops! there was an error');
});
myEmitter.emit('error', new Error('whoops!'));

输出结果:
程序正常退出

whoops! there was an error

demo14: 特殊事件 - newListener

newListener 事件回调方法, 会默认传入两个参数[eventName(事件名称),listener(回调方法)

在 newListener 事件回调方法中进行绑定事件的操作是一件危险的事情(分分钟造成死循环).如果确实要在 newListener 的回调方法中绑定新事件, 用 once 绑定这个危险的回调方法.

下面是一个可以直接执行的 demo

const EventsEmitter = require('events');
class MyEmitter extends EventsEmitter {}
let myEmitter = new MyEmitter();
let times = 0;
myEmitter.on('newListener',(event, listener) =>{
//myEmitter.once('newListener',(event, listener) =>{
    console.log('running times:' + times++);
    console.log(event);
    console.log(listener);
    myEmitter.on('me',()=>{})
});
myEmitter.on('me',()=>console.log('start running newListener:'));
myEmitter.emit('me');

输出结果:
程序正常退出

程序陷入死循环,执行了不到一秒就崩溃了

demo15: 特殊事件 - removeListener

这个特殊事件已经(我学习的时候20171021)变成 stability:0 - deprecated;,就是不靠谱的, 移除已经在计划中

就不考虑这个了

This blog is under a CC BY-NC-SA 3.0 Unported License
本文链接:http://teeeemoji.xyz/2017/10/21/learn-emitter/