创建异步进程
以下四个方法均放回ChildProcess的实例
exec
child_process.exec(command[, options][, callback])
注意:
1、command 是一个 shell 命令的字符串,包含了命令的参数
2、可以使用 callback;
3、衍生一个 shell 然后在该 shell 中执行 command,command 一般是 shell 内置的 命令,如 ls,cat 等,也可以是shell脚本组成的文件,如 start.sh 等
// 回调函数版
const { exec } = require('child_process');
exec('cat *.js missing_file | wc -l', (error, stdout, stderr) => {
if (error) {
console.error(`执行出错: ${error}`);
return;
}
console.log(`stdout: ${stdout}`);
console.log(`stderr: ${stderr}`);
});
// promise 版
const util = require('util');
const exec = util.promisify(require('child_process').exec);
async function lsExample() {
const { stdout, stderr } = await exec('cat *.js missing_file | wc -l');
console.log('stdout:', stdout);
console.log('stderr:', stderr);
}
lsExample();
execFile
child_process.execFile(file[, args][, options][, callback])
注意:
1、与 exec 的不同是,命令的参数不能放在第一个参数,只能作为第二个参数传递;
2、默认情况下不会衍生 shell,指定的可执行 file 直接作为新进程衍生,使其比 child_process.exec() 稍微更高效
3、file 是要运行的可执行文件的名称或路径,如 node.exe,不能是 start.js 这种脚本文件
// 回调函数版
const { execFile } = require('child_process');
const child = execFile('node', ['--version'], (error, stdout, stderr) => {
if (error) {
throw error;
}
console.log(stdout);
});
// promise 版
const util = require('util');
const execFile = util.promisify(require('child_process').execFile);
async function getVersion() {
const { stdout } = await execFile('node', ['--version']);
console.log(stdout);
}
getVersion();
fork
child_process.fork(modulePath[, args][, options])
注意:
1、该接口专门用于衍生新的 Node.js 进程
2、modulePath 是要在node子进程中运行的模块,由于是 node.js 的进程,所以可以是 start.js 这种 js 文件
3、无回调,参数要以第二个参数传入
4、返回的子进程将内置一个额外的ipc通信通道,允许消息在父进程和子进程之间来回传递。
// IPC 通道
// parent.js
var child_process = require('child_process');
var child = child_process.fork('./child.js');
child.on('message', function(m){
console.log('message from child: ' + JSON.stringify(m));
});
child.send({from: 'parent'});
// child.js
process.on('message', function(m){
console.log('message from parent: ' + JSON.stringify(m));
});
process.send({from: 'child'});
// 运行 node parent.js的 result
message from child: {"from":"child"}
message from parent: {"from":"parent"}
spawn
child_process.spawn(command[, args][, options])
注意:使用给定的 command 衍生一个新进程,并带上 args 中的命令行参数。
var spawn = require('child_process').spawn;
var child = spawn('bad_command');
child.on('error', (err) => {
console.log('Failed to start child process 1.');
});
var child2 = spawn('ls', ['nonexistFile']);
child2.stderr.on('data', function(data){
console.log('Error msg from process 2: ' + data);
});
child2.on('error', (err) => {
console.log('Failed to start child process 2.');
});
四个方法的区别
主要的区别在于第一个参数的含义不同:
对于 exec 和 execFile 传递的是 command 或 可执行文件,类似 ls 或者 start.sh 或者 node;可接受回调; 二者不同的是命令的参数位置
对于 fork 传递的是node支持的脚本,类似 start.js,无回调
对于 spawn,它是以上三个方法实现的基础。
对于构建开发环境而言,一般至少会起两个进程,主进程起 devServer;还需一个进程起mock API 服务器,所以一般用 fork 较多
关于 ChildProcess 类
1、ChildProcess
类的实例都是 EventEmitter
,表示衍生的子进程
2、每个实例都有 stdout, stdin, stderr 三个流对象
3、通过 fork 产生的子进程与父进程可以用 send 方法及监听 message 事件来相互通信
4、 几个事件: error , message , exit , close , disconnect
todo
https://zhuanlan.zhihu.com/p/64205442
teen_process
//安装
npm i teen_process
Node 的 child_process 的成熟版本。exec
确实很有用,但它有很多限制。这是在后台使用的 es7 ( async
/ await
) 实现。它负责包装命令和参数,因此我们不必关心转义空格。即使命令失败或超时,它也可以返回 stdout/stderr。重要的是,它也不容易受到最大缓冲区问题的影响。
teen_process.exec
import { exec } from 'teen_process';
// basic usage
let {stdout, stderr, code} = await exec('ls', ['/usr/local/bin']);
console.log(stdout.split("\n")); // array of files
console.log(stderr); // ''
console.log(code); // 0
// works with spaces
await exec('/command/with spaces.sh', ['foo', 'argument with spaces'])
// as though we had run: "/command/with spaces.sh" foo "argument with spaces"
// nice error handling that still includes stderr/stdout/code
try {
await exec('echo_and_exit', ['foo', '10']);
} catch (e) {
console.log(e.message); // "Exited with code 10"
console.log(e.stdout); // "foo"
console.log(e.code); // 10
}
该exec
函数采用一些选项,具有以下默认值:
{
cwd: undefined,
env: process.env,
timeout: null,
killSignal: 'SIGTERM',
encoding: 'utf8',
ignoreOutput: false,
stdio: "inherit",
isBuffer: false,
shell: undefined,
logger: undefined,
maxStdoutBufferSize: 100 * 1024 * 1024, // 100 MB
maxStderrBufferSize: 100 * 1024 * 1024, // 100 MB
}
其中大部分是不言自明的。ignoreOutput
如果您有一个非常健谈的进程,您不关心其输出并且不想将其添加到程序消耗的内存中,则此方法很有用。
需要两个缓冲区大小限制以避免在收集进程输出时内存溢出。如果不同流类型的输出块的总大小超过给定的块,那么最旧的块将被拉出以将内存负载保持在可接受的范围内。
如果您使用的是Windows,你想传递shell: true
的,因为exec
实际使用spawn
引擎盖下的,因此受到有关Windows +提到的问题spawn
中的节点文档。
如果stdio
选项未设置为inheirt
,您可能无法从流程中获得彩色输出。在这种情况下,您可以浏览子流程的文档以查看是否可以指定类似--colors
或FORCE_COLORS
可以指定的选项。您也可以尝试设置env.FORCE_COLOR = true
并查看它是否有效。
例子:
try {
await exec('sleep', ['10'], {timeout: 500, killSignal: 'SIGINT'});
} catch (e) {
console.log(e.message); // "'sleep 10' timed out after 500ms"
}
该isBuffer
选项指定返回的标准 I/O 是Buffer 的一个实例。
例子:
let {stdout, stderr} = await exec('cat', [filename], {isBuffer: true});
Buffer.isBuffer(stdout); // true
该logger
选项允许将 stdout 和 stderr 发送到接收到的特定记录器。这被ignoreOutput
选项覆盖。
teen_process.SubProcess
spawn
已经非常棒了,但对于某些用途,有相当多的样板文件,尤其是在async/await
上下文中使用时。teen_process
还公开了一个SubProcess
类,可用于减少一些样板。它有两种方法,start
以及stop
:
import { SubProcess } from 'teen_process';
async function tailFileForABit () {
let proc = new SubProcess('tail', ['-f', '/var/log/foo.log']);
await proc.start();
await proc.stop();
}
在调用上下文中引发启动/停止错误。
事件
您可以收听 8 个事件:
exit
stop
end
die
output
lines-stdout
lines-stderr
stream-line
proc.on('exit', (code, signal) => {
// if we get here, all we know is that the proc exited
console.log(`exited with code ${code} from signal ${signal}`);
// exited with code 127 from signal SIGHUP
});
proc.on('stop', (code, signal) => {
// if we get here, we know that we intentionally stopped the proc
// by calling proc.stop
});
proc.on('end', (code, signal) => {
// if we get here, we know that the process stopped outside of our control
// but with a 0 exit code
});
proc.on('die', (code, signal) => {
// if we get here, we know that the process stopped outside of our control
// with a non-zero exit code
});
proc.on('output', (stdout, stderr) => {
console.log(`stdout: ${stdout}`);
console.log(`stderr: ${stderr}`);
});
// lines-stderr is just the same
proc.on('lines-stdout', lines => {
console.log(lines);
// ['foo', 'bar', 'baz']
// automatically handles rejoining lines across stream chunks
});
// stream-line gives you one line at a time, with [STDOUT] or [STDERR]
// prepended
proc.on('stream-line', line => {
console.log(line);
// [STDOUT] foo
});
// so we could do: proc.on('stream-line', console.log.bind(console))
启动探测器
如何SubProcess
知道何时从 返回控制权start()
?嗯,默认是等到有一些输出。您还可以传入一个数字,这将导致它等待那个毫秒数,或者一个函数(我称之为 a startDetector
),它接受 stdout 和 stderr 并在您想要控制时返回 true 。例子:
await proc.start(); // will continue when stdout or stderr has received data
await proc.start(0); // will continue immediately
let sd = (stdout, stderr) => {
return stderr.indexOf('blarg') !== -1;
};
await proc.start(sd); // will continue when stderr receives 'blarg'
startDetector
如果要声明启动失败,自定义也可以抛出错误。例如,如果我们知道第一个输出可能包含一个使进程无效的字符串(对我们来说),我们可以定义一个自定义startDetector
如下:
let sd = (stdout, stderr) => {
if (/fail/.test(stderr)) {
throw new Error("Encountered failure condition");
}
return stdout || stderr;
};
await proc.start(sd); // will continue when output is received that doesn't
// match 'fail'
最后,如果要指定等待进程启动的最长时间,可以通过将第二个参数(以毫秒为单位)传递给start()
:
// use the default startDetector and throw an error if we wait for more than
// 1000ms for output
await proc.start(null, 1000);
精加工工艺
进程启动后,您可以使用join()
等待它自行完成:
await proc.join(); // will throw on exitcode not 0
await proc.join([0, 1]); // will throw on exitcode not 0 or 1
杀死进程怎么样?您可以提供自定义信号,而不是使用默认信号SIGTERM
吗?为什么是:
await proc.stop('SIGHUP');
如果您的进程可能无法杀死并且您并不真正关心,您还可以传递超时,它会在超时过后以错误的形式将控制权返回给您:
try {
await proc.stop('SIGHUP', 1000);
} catch (e) {
console.log("Proc failed to stop, ignoring cause YOLO");
}
总而言之,这使得编写一个脚本将文件拖尾 X 秒然后停止,使用 async/await 和非常简单的错误处理变得非常简单。
async function boredTail (filePath, boredAfter = 10000) {
let p = new SubProcess('tail', ['-f', filePath]);
p.on('stream-line', console.log);
await p.start();
await Bluebird.delay(boredAfter);
await p.stop();
}