事件驱动架构

预计阅读时间: 7 分钟

本篇文章我们来说一说 nodejs 的另外一个很重要的特点,也就是事件驱动。

首先需要知道事件驱动的架构,它其实就是建立在软件开发之上的一种通用的模式,那为了方便理解,我们这里也去将它跟日常所说的发布订阅,或者说观察者模式去进行类比,但是也要强调一下,在实践过程中,这三者其实并不是一回事,那只不过他们在使用的时候都会有一个共同的特征,那就是发布者来广播消息,而其他的订阅者就可以去监听到之前所订阅的消息,从而在订阅的事件发生之后再去执行相应的处理程序,那知道了事件驱动这样的一个大体的模式之后,我们就来再具体的去说一下在 nodejs 中的事件驱动的一个具体使用。

在这之前我们去介绍 异步 io 的时候,我们说过 nodejs 是诞生之初的目的,就是为了去实现高性能的 web 服务,而它实现高性能的一个主要表现手段,就是拥有了一套单线程下的一个异步非阻塞的 io 机制,但是也正是这个异步非阻塞的 io 实现,让我们在去编写 nodejs 代码的过程中,就会去编写很多的异步代码,而由于又是非阻塞的,因此程序代码在执行的过程中,业务层他拿到的并不是最终的目标数据,所以等到同步代码执行完成之后,那底层的 libuv 库就开始去工作了,那我们也可以认为在这个 libuv 库里边,其实就有两个非常重要的内容,一个就是 event loop(事件循环),还有一个就是 event queen(事件队列),就像我们图中看到的这样,当然这里我们还是以 io 操作为例,因为在 node 当中,那同样也存在着很多非 io 的异步操作,比如我们最常见的 setTimeout,那当 libuv 库接收到了一个异步操作的请求之后,多路分解器就会去进行工作,首先他会去找到当前平台环境下可用的 io 处理接口,然后在等待着 io 操作结束之后,将相应的事件去通过事件循环也好,或者其他的方式添加到我们的这个事件队列当中,那在这个过程中,事件循环是一直工作的,最后他会按照一定的顺序再从事件队列中去取出相应的事件,然后再交给主线程来进行执行。

那在这个过程中,事件驱动的体现就是有人发布了事件,然后订阅这个事件的人在将来去接收到具体的事件,消息发布之后,就会去执行订阅时所注册的处理程序,那这样的架构就很好的去解决了在 nodejs 当中,由异步非阻塞操作所带来的这样一个数据最终获取的问题,那具体的代码实现的就是在 nodejs 中它内置了一个 events 模块,那接下来这里我们就会去通过代码的方式来快速的演示一下,在 node 中这种基于事件的操作行为,那至于它的源码实现和应用的一些细节,我们会在后续再去进行具体的说明。

const EventEmitter = require('events')

const myEvent = new EventEmitter()

myEvent.on('event1', (...ars) => {
  console.log('事件1执行了: ', ars)
})

myEvent.on('event1', (...ars) => {
  console.log('事件1又执行了: ', ars)
})

setTimeout(() => {
  myEvent.emit('event1', '数据1', '数据2')
}, 2000)

整个逻辑其实非常简单,主要的核心思想就是说明一下我们 nodejs 采用了事件驱动的架构,而具体的表现就在这里边,我们有一个 events 这样的一个模块,它是内置的核心的,已经提供好了,我们只要去用就行了,跟常见的发布订阅差不多,那在这一块的话就可以通过new EventEmitter()这样的一个实例对象,然后通过 on 和 emit 一个形式来订阅事件、触发事件,这个事件触发之后,回调函数它就会被执行到,我们其实只需要去知道有一个发布者,然后他可以有多个或者说一个订阅者,这个代码从上往下同步的代码会先去执行,等到这些所有的同步代码都执行完成之后,两秒后回去触发事件,那这个时候就会按照我们注册的顺序来执行相应的事件处理函数。

我们并没有去过多的讨论,在我们这个事件循环当中不同类型的事件的执行顺序,只是去先说明一下,在实现异步 IO 的前提下,然后我们再配合的事件驱动的操作,那 nodejs 就可以很方便的去进行异步编程了,那并且还可以很容易地拿到我们这个异步编程它返回的执行结果,这些特点也就为 nodejs 去实现高性能的 web 服务去提供了一个前提。