Nodejs 第十七章 events

255 阅读7分钟

在 Node.js 中,events 模块是一个允许不同部分的应用程序通过发射和监听事件来进行通信的核心模块。这个模块特别关键,因为它支持构建基于事件的架构,这对于处理异步操作尤其重要

  • events用的其实不多,但很多API的底层都有集成了这个events,所以才导致明面上用的不是很多
  • 学习这部分的内容不在于追求使用,而是探索events的一个架构设计

基础概念

  • EventEmitter 类events 模块的核心是 EventEmitter 类。这个类用于创建能够发射(emit)事件的对象,并且这些对象可以拥有多个监听(监听器是函数,当指定事件被触发时会被调用)。

创建和使用 EventEmitter

//引入模块和创建实例
const EventEmitter = require('events');
const emitter = new EventEmitter();

//监听事件:使用 .on() 或 .addListener() 方法来添加事件监听器
emitter.on('event', function(message) {
  console.log(`Received message: ${message}`);
});

//发射事件:使用 .emit() 方法来触发事件。这个方法允许传递任意数量的参数给监听器函数
emitter.emit('event', 'Hello, world!');

事件模型

  • Nodejs事件模型采用了,发布订阅设计模式

image-20240414080349825

想象一下你正在举办一个派对,你告诉了你的朋友们,只要你发出邀请,他们就可以来参加(这就是“订阅”)。当派对的日期确定了(“发布”事件),你就会通知(“发出”)所有表达了兴趣的朋友们。

在 Node.js 中,发布-订阅模式也是这样工作的:

  • 订阅者(Subscribers):图中的“订阅 test1”和“订阅 test2”代表两个功能或组件,它们“订阅”或者说监听某些事件,等待事件被触发。在代码里,这是通过在 EventEmitter 实例上添加事件监听器实现的。
  • 发布者(Publishers):当特定事件发生时,如“事件中心”收到特定的信号,它就会“发布”事件。在 Node.js 中,这通常通过调用 emit 方法来实现。
  • 事件中心(Event Center):这是事件的调度中心,在 Node.js 中通常由 EventEmitter 类实例化的对象来充当。它负责接收订阅者的监听器,并在适当的时刻触发这些监听器。

在图中,“订阅 test1”和“订阅 test2”可能代表了两段代码,它们在等待“事件中心”通知它们某些事情发生了。当“事件中心”使用 emit 方法发出“test1”或“test2”事件时,相关的函数或代码块就会被执行。这就好比你向“订阅”了派对的朋友发送邀请,收到邀请的朋友就会按照通知来到派对。

总的来说,发布-订阅模式是一种解耦合的设计模式,它允许不同的系统组件独立地处理事件的订阅和发布,增加了代码的灵活性和可扩展性。

EventEmitter 核心API 方法

API描述示例
on为事件注册一个监听器,该监听器每次事件发生时都会被调用。emitter.on('data', callback)
once为事件注册一个一次性监听器,事件发生一次后监听器解除绑定。emitter.once('open', callback)
emit触发事件,导致添加到该事件的所有监听器被调用。emitter.emit('data', 'This is a message')
off移除特定事件的一个监听器。emitter.off('data', callback)

案例1(on与emit结合)

  • 下面这个是一个很简单的案例:
    • 其中,监听test就是订阅事件,我们可以理解为交了一个朋友,这个test就是朋友的名字,我们通过了on这个API和朋友建立起了联系,而回调函数就是朋友收到我们给的消息。
    • emit是发布事件,可以理解为我们给朋友发消息,他会通过我们的联系渠道(on的回调)得到该消息
const EventEmitter = require('events');

//用法跟vue2的event bus(全局事件总线)或者第三方库mitt是差不多的,因为采用的都是发布订阅模式
//EventEmitter是一个class类,所以需要创造一个实例来进行使用
const event = new EventEmitter()
//监听test,这个test名字可以随便起,然后接收一个回调函数。这是订阅事件
event.on('test',(data)=>{
    console.log(data)
})

//这是发布事件
event.emit('test','小余歇菜啦','小满穿黑丝') //派发事件

image-20240414102444848

案例2(once与emit结合)

  • 那通过前面,我们已经形象的知道on作用了。那on和once他们的区别就在于是不是一次性的,那这又是什么意思?
    • once可以理解为,联系了一次就友尽了的朋友,你们的友谊才刚开始就歇菜了。就像你跟你的异性好朋友进行了表白,但很遗憾,失败了,你们之间连朋友都做不成了捏。而once就相当于你这次表白失败的联系,表白失败了之后,以后从此是路人(拉黑删除一条龙服务)~

image-20240414103454048

  • once的情况如下:

image-20240414103710627

off

  • off这个就厉害了,其实就相当于你把你跟你朋友的联系渠道断掉了,那你发给你朋友的消息就成了无根之源了,自然也就不了了之(on或者once的回调就拿不到信息了,因为发送的信息和联系渠道都被断掉了)
    • 我们这里需要清楚一个概念,fn是传递消息内容的,并不具备唯一性,是能够复用的。我们关键还是通过第一个参数,来确定要断掉哪个监听器

image-20240414104820886

默认监听数(on)

  • on默认情况下,最多只能10个,超过就报错
const EventEmitter = require('events');

const event = new EventEmitter()

//下面这部分 x11倍
event.on('test', (data) => {
    console.log(data)
})

image-20240414105418055

  • 那如何解除限制?调用 setMaxListeners 传入新的上限数量
event.setMaxListeners(20)//等到20个的时候,再报警告

image-20240414105709811

process源码

  • 在process中,其实也有这四个核心的API,是一模一样的
  • 我们打开nodejs 源码 搜索 setupProcessObject 这个函数(位于lib>internal>bootstrap>node.js下)

image-20240414110139041

Process这个对象实现了以下步骤:

  1. 引入 Event 模块
    • setupProcessObject 函数首先引入了 Node.js 的 events 模块。这个模块提供了事件发射和处理的功能,是 Node.js 异步事件驱动架构的基础。
  2. 获取和修改 process 的原型
    • 函数获取 process 对象的原型,并将 events 模块的 EventEmitter 的原型对象赋予给 process 的原型。这一步是关键,因为它使得整个 process 对象继承了事件发射器的功能,允许 process 对象能够发射和监听事件,如 exit, uncaughtException 等。
    • 这里是涉及到JS高级的原型链知识,不熟悉的可以针对性补一下
  3. 重新绑定上下文
    • 通过将事件模块的原型赋予给 process,并且在这个过程中正确地绑定上下文,确保了当 process 对象上的事件被触发时,事件处理函数的执行上下文(this 指针)指向正确的 process 对象。
  4. process 挂载到 globalThis
    • 最后,setupProcessObject 函数将 process 对象挂载到 globalThis 上,使其成为一个全局可访问的 API。这意味着在 Node.js 的任何模块中,开发者都可以直接使用 process 来访问其方法和属性,无需任何额外的引入或声明。

综合上面的步骤,setupProcessObject 函数最重要的功能是为 process 对象赋予事件处理的能力,并确保这个关键的全局对象可以在 Node.js 的任何地方被访问和使用。为 Node.js 的事件驱动模型提供了基础,并且使进程级的交互成为可能,如处理信号、环境变量、程序参数等。这些都是 Node.js 应用正常运行和管理所必需的。