ChildProcess 关闭、退出事件之间的区别

Difference between ChildProcess close, exit events

在 Node.js 中通过 spawn()/exec()/... 生成子进程时,子进程上有一个 'close' 和一个 'exit' 事件。

这两者有什么区别,什么时候需要用什么?

你看过文档了吗?

根据this

当子进程的 stdio 流关闭时,将发出 'close' 事件。 这与 'exit' 事件不同,因为多个进程可能共享相同的 stdio 流

子进程结束后触发'exit'事件。如果进程退出,则code为进程的最终退出代码,否则为null。如果进程因收到信号而终止,则 signal 是信号的字符串名称,否则为 null。两者之一将始终为非空。

简短版本是,'exit' 在 child 退出但 stdio 尚未 关闭时发出。 'close' 在 child 退出时发出 并且 它的 stdios 关闭。

此外,他们共享相同的签名。

在 Node.js 0.7.7 之前,child 进程上只有一个 "exit" 事件(没有 "close" 事件)。当 child 进程退出并且所有流(stdin、stdout、stdout)关闭时,将触发此事件。

In Node 0.7.7, the "close" event was introduced (see commit)。 documentation (permalink) 当前表示:

The 'close' event is emitted when the stdio streams of a child process have been closed. This is distinct from the 'exit' event, since multiple processes might share the same stdio streams.

如果您只是生成一个程序而不对 stdio 做任何特殊的事情,"close" 事件会在 "exit" 之后触发。 "close" 事件可以延迟,例如stdout 流通过管道传输到另一个流。所以这意味着 "close" 事件可以在 "exit" 事件之后(无限期地)延迟。
这是否意味着 "close" 事件总是在 "exit" 之后触发?正如下面的例子所示,答案是否定的。

因此,如果您只对进程终止感兴趣(例如,因为进程拥有独占资源),监听 "exit" 就足够了。 如果您不关心程序,只关心它的输入 and/or 输出,请使用 "close" 事件。

实验:在杀死之前销毁 stdio child

实验上(在 Node.js v7.2.0 中),我发现如果 child 进程不使用 stdio 流,那么 "close" 事件只会在之后触发程序已退出:

// The "sleep" command takes no input and gives no output.
cp = require('child_process').spawn('sleep', ['100']);
cp.on('exit', console.log.bind(console, 'exited'));
cp.on('close', console.log.bind(console, 'closed'));
cp.stdin.end();
cp.stdout.destroy();
cp.stderr.destroy();
console.log('Closed all stdio');
setTimeout(function() { 
    console.log('Going to kill');
    cp.kill();
}, 500);

上面的程序生成 "sleep" 输出:

Closed all stdio
Going to kill
exited null SIGTERM
closed null SIGTERM

当我将第一行更改为仅输出的程序时,

// The "yes" command continuously outputs lines with "y"
cp = require('child_process').spawn('yes');

...那么输出是:

Closed all stdio
exited 1 null
closed 1 null
Going to kill

类似地,当我更改 spawn 一个只从 stdin 读取的程序时,

// Keeps reading from stdin.
cp = require('child_process').spawn('node', ['-e', 'process.stdin.resume()']);

或者当我从标准输入读取并输出到标准输出时,

// "cat" without arguments reads from stdin, and outputs to stdout
cp = require('child_process').spawn('cat');

实验:将程序通过管道传输到另一个程序,杀死第一个程序

之前的实验比较人为。下一个实验更现实一些:将一个程序通过管道传输到另一个程序并杀死第一个程序。

// Reads from stdin, output the input to stdout, repeat.
cp = require('child_process').spawn('bash', ['-c', 'while read x ; do echo "$x" ; done']);
cp.on('exit', console.log.bind(console, 'exited'));
cp.on('close', console.log.bind(console, 'closed'));

cpNext = require('child_process').spawn('cat');
cp.stdout.pipe(cpNext.stdin);

setTimeout(function() {
    // Let's assume that it has started. Now kill it.
    cp.kill();
    console.log('Called kill()');
}, 500);

输出:

Called kill()
exited null SIGTERM
closed null SIGTERM

类似地,当第一个程序仅从输入读取而从不输出时:

// Keeps reading from stdin, never outputs.
cp = require('child_process').spawn('bash', ['-c', 'while read ; do : ; done']);

当第一个程序不等待标准输入而继续输出时,行为有所不同,正如下一个实验所示。

实验:将具有大量输出的程序通过管道传输到另一个程序,杀死第一个程序

// Equivalent to "yes | cat".
cp = require('child_process').spawn('yes');
cp.on('exit', console.log.bind(console, 'exited'));
cp.on('close', console.log.bind(console, 'closed'));

cpNext = require('child_process').spawn('cat');
cp.stdout.pipe(cpNext.stdin);

setTimeout(function() {
    // Let's assume that it has started. Now kill it.
    cp.kill();
    console.log('Called kill()');
    setTimeout(function() {
        console.log('Expecting "exit" to have fired, and not "close"');
        // cpNext.kill();
        // ^ Triggers 'error' event, errno ECONNRESET.
        // ^ and does not fire the 'close' event!

        // cp.stdout.unpipe(cpNext.stdin);
        // ^ Does not appear to have any effect.
        // ^ calling cpNext.kill() throws ECONNRESET.
        // ^ and does not fire the 'close' event!

        cp.stdout.destroy(); // <-- triggers 'close'
        cpNext.stdin.destroy();
        // ^ Without this, cpNext.kill() throws ECONNRESET.

        cpNext.kill();
    }, 500);
}, 500);

以上程序输出如下然后退出:

Called kill()
exited null SIGTERM
Expecting "exit" to have fired, and not "close"
closed null SIGTERM