Nar*_*igo 27 event-handling child-process node.js
通过spawn()/exec()/...Node.js生成子进程时,子进程上有'close'一个'exit'事件和一个事件.
这两者之间有什么区别,什么时候需要使用什么?
Rob*_*b W 28
在Node.js 0.7.7之前,子进程上只有一个"退出"事件(没有"关闭"事件).子进程退出时将触发此事件,并关闭所有流(stdin,stdout,stdout).
在Node 0.7.7中,引入了"close"事件(请参阅commit).该文件(永久)目前表示:
当关闭子进程的stdio流时,将发出'close'事件.这与'exit'事件不同,因为多个进程可能共享相同的stdio流.
如果你只是生成一个程序并且没有对stdio做任何特殊操作,那么"关闭"事件会在"退出"之后触发.如果例如将stdout流传送到另一个流,则可以延迟"关闭"事件.这意味着"关闭"事件可以在"退出"事件之后(无限期地)延迟.
这是否意味着"退出"事件总是在"退出"之后被触发?如下面的例子所示,答案是否定的.
因此,如果您只对流程终止感兴趣(例如,因为流程拥有独占资源),那么监听"退出"就足够了.如果您不关心程序,只关心其输入和/或输出,请使用"关闭"事件.
通过实验(在Node.js v7.2.0中),我发现如果子进程没有使用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);
Run Code Online (Sandbox Code Playgroud)
上述程序产生"睡眠"输出:
Closed all stdio
Going to kill
exited null SIGTERM
closed null SIGTERM
Run Code Online (Sandbox Code Playgroud)
当我将第一行更改为仅输出的程序时,
// The "yes" command continuously outputs lines with "y"
cp = require('child_process').spawn('yes');
Run Code Online (Sandbox Code Playgroud)
...然后输出是:
Closed all stdio
exited 1 null
closed 1 null
Going to kill
Run Code Online (Sandbox Code Playgroud)
类似地,当我更改spawn一个只从stdin读取的程序时,
// Keeps reading from stdin.
cp = require('child_process').spawn('node', ['-e', 'process.stdin.resume()']);
Run Code Online (Sandbox Code Playgroud)
或者当我从stdin读取并输出到stdout时,
// "cat" without arguments reads from stdin, and outputs to stdout
cp = require('child_process').spawn('cat');
Run Code Online (Sandbox Code Playgroud)
之前的实验非常人为.下一个实验更加现实:您将程序管道传输到另一个程序并终止第一个程序.
// 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);
Run Code Online (Sandbox Code Playgroud)
输出:
Called kill()
exited null SIGTERM
closed null SIGTERM
Run Code Online (Sandbox Code Playgroud)
同样,当第一个程序只从输入读取而从不输出时:
// Keeps reading from stdin, never outputs.
cp = require('child_process').spawn('bash', ['-c', 'while read ; do : ; done']);
Run Code Online (Sandbox Code Playgroud)
当第一个程序在不等待stdin的情况下继续输出时,行为是不同的,如下一个实验所示.
// 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);
Run Code Online (Sandbox Code Playgroud)
上面的程序输出以下内容然后退出:
Called kill()
exited null SIGTERM
Expecting "exit" to have fired, and not "close"
closed null SIGTERM
Run Code Online (Sandbox Code Playgroud)