M K*_*atz 2 asynchronous spawn node.js
在Node 的 Child Process spawn()function的文档以及我在其他地方看到的示例中,模式是调用该spawn()函数,然后在返回的对象上设置一堆处理程序ChildProcess。例如,这是spawn()该文档页面上给出的第一个示例:
const { spawn } = require('child_process');
const ls = spawn('ls', ['-lh', '/usr']);
ls.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});
ls.stderr.on('data', (data) => {
console.error(`stderr: ${data}`);
});
ls.on('close', (code) => {
console.log(`child process exited with code ${code}`);
});
Run Code Online (Sandbox Code Playgroud)
该spawn()函数本身在第二行调用。我的理解是spawn()异步启动子进程。从文档中:
child_process.spawn() 方法使用给定命令生成一个新进程,并在 args 中使用命令行参数。
然而,上面脚本的以下几行继续为该进程设置各种处理程序,因此假设在spawn()第 2 行调用时间和其他事情发生之间该进程尚未实际启动(并且可能完成)随后的几行。我知道 JavaScript/Node 是单线程的。然而,操作系统不是单线程的,天真地人们会认为该spawn()调用是告诉操作系统立即生成进程(此时,不幸的是,操作系统可能会挂起父 Node 进程并运行/完成执行下一行 Node 代码之前的子进程)。
但一定是在当前 JavaScript 函数完成(或者更一般地说,调用当前函数的当前 JavaScript 事件处理程序完成)之前,该进程实际上不会生成,对吧?
这似乎是一件非常重要的事情。为什么在子进程文档页面中没有这样说?是否有一些压倒性的 Node 原则使得没有必要明确说明这一点?
新进程的生成立即开始(它被移交给操作系统来实际启动该进程并使其运行)。启动新进程.spawn()是异步且非阻塞的。因此,它将向操作系统发起操作并立即返回。您可能认为这就是为什么可以在返回后设置事件处理程序(因为该进程尚未完成启动)。嗯,是的,也不是。它可能尚未完成新进程的启动,但这并不是它正常的主要原因。
没关系,因为 Node.js 通过单线程事件队列运行其所有事件。因此,在代码完成执行并将控制权返回给系统之前,无法处理新生成的进程中的任何事件。只有这样,它才能处理事件队列中的下一个事件并触发您为其注册处理程序的事件之一。
或者,换句话说,来自其他进程的所有事件都不是先发制人的。他们不会/不能中断您现有的 Javascript 代码。因此,由于您仍在运行 Javascript 代码,因此这些事件还无法运行。相反,它们位于事件队列中,直到您的 Javascript 代码完成,然后解释器可以从事件队列中获取下一个事件并运行与其关联的回调。同样,该回调运行直到它返回到解释器,然后解释器可以获得下一个事件并运行其回调等等......
这就是为什么 Node.js 被称为事件驱动系统。
因此,做这种类型的结构是完全可以的:
const { spawn } = require('child_process');
const ls = spawn('ls', ['-lh', '/usr']);
ls.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});
ls.stderr.on('data', (data) => {
console.error(`stderr: ${data}`);
});
ls.on('close', (code) => {
console.log(`child process exited with code ${code}`);
});
Run Code Online (Sandbox Code Playgroud)
在您的代码完成并将控制权返回给系统之前,这些data或事件都无法执行其回调。close因此,像您一样设置这些事件处理程序是完全安全的。即使新生成的进程正在运行并立即生成事件,这些事件也只会位于事件队列中,直到您的 Javascript 完成其正在执行的操作(包括设置事件处理程序)。
现在,如果您延迟设置事件处理程序,直到事件循环的未来某个时间点(如下所示)使用类似 a 的东西setTimeout(),那么您可能会错过一些事件:
const { spawn } = require('child_process');
const ls = spawn('ls', ['-lh', '/usr']);
setTimeout(() => {
ls.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});
ls.stderr.on('data', (data) => {
console.error(`stderr: ${data}`);
});
ls.on('close', (code) => {
console.log(`child process exited with code ${code}`);
});
}, 10);
Run Code Online (Sandbox Code Playgroud)
在这里,您不会立即将事件处理程序设置为事件循环的同一滴答的一部分,而是在短暂的延迟之后。因此,在安装事件处理程序之前,某些事件可能会从事件循环中得到处理,并且您可能会错过其中一些事件。显然,你永远不会这样做(故意),但我只是想表明,在事件循环的同一滴答上运行的代码没有问题,但在事件循环的某个未来滴答上运行的代码可能会出现问题缺少事件的问题。