节点JS异步执行和事件发射/侦听器模型

时间:2018-07-17 22:33:00

标签: javascript node.js raspberry-pi serial-port

我是Node JS的新手,正在尝试了解Node的并发/异步执行模型。

到目前为止,我确实了解到,每当在Node中遇到异步任务时,该任务就会在后台运行(例如,异步setTimeout函数将开始计时),然后将控件发送回该任务。调用堆栈。一旦计时器超时,传递给异步任务的回调将被推送到回调队列中,并且一旦调用堆栈为空,该回调就会被执行。我借助this可视化来了解任务执行的顺序。到目前为止,一切都很好。

Q1。现在,我无法全神贯注于事件侦听器和事件发射器的范式,如果有人能解释一下甚至发射器和侦听器如何落入调用堆栈,事件循环和回调队列的画面,我将不胜感激。

Q2。我有以下代码从树莓派的串行端口读取数据。

 const SerialPort = require('serialport');

const port = new SerialPort('/dev/ttyUSB0',{baudRate: 9600}, (err) => {
    if (err) {
        console.log("Port Open Error: ", err);
    }
} )


port.on('data', (data) => {
    console.log(data.toString());
})
从示例中可以看出,为了从串行端口读取数据,已使用“事件监听器”。据我了解,每当数据到达端口时,都会发出一个“数据”事件,该事件“响应”或被监听器监听,该事件只是将数据打印到控制台上。

当我运行上述程序时,它会连续运行,并且不会中断,只要有数据到达串行端口,它就会将数据打印到控制台上。没有像循环程序那样连续循环扫描串行端口的情况下连续运行。所以我的问题是,为什么该程序连续运行?显然,事件发射器正在连续运行,每当有数据到达时都会生成一个事件,并且事件侦听器也正在连续运行,每当发出“数据”事件时就打印数据。但是这些东西实际上还在连续地在哪里运行?这些东西如何适合调用/执行堆栈,事件循环和回调队列的整个画面?

谢谢

1 个答案:

答案 0 :(得分:0)

  

Q1。现在,我无法全神贯注于事件侦听器和事件发射器的范式,如果有人能解释一下甚至发射器和侦听器如何落入调用堆栈,事件循环和回调队列的画面,我将不胜感激。

事件发射器本身与事件循环无关。每当有人发出事件时,都会同步调用事件侦听器。当某些代码调用someEmitter.emit(...)时,从.emit()接连发生时起,所有侦听器都被同步调用。这只是普通的旧函数调用。您可以自己查看eventEmitter代码,以查看一个for循环,该循环一个接一个地调用与给定事件相关联的所有侦听器。

  

Q2。我有以下代码从树莓派的串行端口读取数据。

代码中的data事件是一个异步事件。这意味着它将在将来的未知时间触发一次或多次。一些较低级别的代码将被注册用于某种I / O事件。如果该代码是本机代码,则它将在node.js事件队列中插入一个回调。当node.js运行完其他代码后,它将从事件队列中获取下一个事件。当涉及到与串行端口上可用数据相关联的事件时,它将调用port.emit(...),并会同步触发每个侦听器,以调用data事件。

  

当我运行上述程序时,它会连续运行,并且不会中断,只要有数据到达串行端口,它就会将数据打印到控制台上。没有像循环程序那样连续循环扫描串行端口的情况下连续运行。所以我的问题是,为什么该程序连续运行?

简而言之,这是node.js的事件驱动性质。您对某些事件感兴趣。较低层的代码会看到传入的数据已到达并触发这些事件,从而调用您的侦听器。

这是Javascript解释器管理事件循环的方式。运行当前的Javascript,直到完成为止。检查事件循环中是否还有其他事件。如果是这样,请抓住下一个事件并运行它。如果没有,请等待事件队列中有一个事件,然后运行它。

  

很明显,事件发射器正在连续运行,每当数据到来时都会生成一个事件,并且事件侦听器也在连续运行,每当发出“数据”事件时就打印数据。但是这些东西实际上还在连续地在哪里运行?

事件发射器本身未连续运行。这只是一种通知方案(本质上是一种发布/订阅模型),其中一方可以使用.on()注册对某些事件的兴趣,而另一方可以使用.emit()来触发某些事件。它允许通过通用接口进行非常松散的耦合。发射器系统中没有任何东西连续运行。这只是一个通知方案。某人使用.emit()触发了一个事件,它查看其数据结构以查看谁对此事件进行了注册并进行了调用。它对事件,数据本身或触发方式一无所知。发射器的工作只是向有兴趣的人发送通知。

到目前为止,我们已经描述了Java方面的工作原理。它如上所述运行事件循环。在较低级别上,存在直接与串行接口连接的串行端口代码,这很可能是一些本机代码。如果操作系统支持串行端口的本机异步接口,则本机代码将使用该接口,并在串行端口上等待数据时告诉操作系统调用它。如果操作系统中没有用于串行端口数据的本机异步接口,则本机代码中可能存在一个本机线程,该线程与处理从端口获取数据的串行端口接口,可以通过轮询或使用其他机制进行处理内置在硬件中,告诉您何时有数据可用。工作原理的确切细节将内置到您使用的串行端口模块中。

  

这些东西如何适合调用/执行堆栈,事件循环和回调队列的整个画面?

解释器在Javascript事件队列中发现一个事件并开始执行该调用/执行堆栈。执行该事件将始终以Javascript回调开始。解释器将调用该回调(将返回地址放入调用/执行堆栈中)。该回调将一直运行直到返回。返回时,调用/执行堆栈将为空。然后,解释器将检查事件队列中是否还有其他事件在等待。如果是这样,它将运行那个。


仅供参考,如果您要检查正在使用的串行端口模块的代码,则为all there on Github。它似乎确实具有许多本机代码文件。您会看到file called poller.cpp here,它似乎使用libuv提供的node.js附加编程接口进行协作轮询。例如,它创建一个uv_poll_t,它是一个here描述的轮询句柄。以下是that doc的摘录:

  

轮询句柄用于监视文件描述符的可读性,可写性和断开性,类似于poll(2)的目的。

     

轮询句柄的目的是允许集成依赖于事件循环的外部库,以向其发出有关套接字状态变化的信号,例如c-ares或libssh2。不建议将uv_poll_t用于任何其他目的。 uv_tcp_t,uv_udp_t等提供的实现比uv_poll_t可以实现的实现更快,更具可伸缩性,尤其是在Windows上。

     

轮询可能会偶尔发出信号,表明文件描述符是可读或可写的,即使不是。因此,当用户尝试从fd读取或写入fd时,应始终准备好处理EAGAIN或同等功能。