child_process

创建异步进程

以下四个方法均放回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,您可能无法从流程中获得彩色输出。在这种情况下,您可以浏览子流程的文档以查看是否可以指定类似--colorsFORCE_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();
}

   转载规则


《child_process》 锦泉 采用 知识共享署名 4.0 国际许可协议 进行许可。
  目录