child_process

以下均未考虑windows

child_process 模块可以衍生出子进程,我们可以用子进程来做一些事情从而不影响主进程。

创建进程的几种方法

异步方法

  • .exec()
  • .execFile()
  • .fork()
  • .spawn()

前三种都是通过.spawn()实现的,调用以上方法后都会返回ChildProcess的实例,这些实例实现了 Node.js EventEmitter API,允许父进程注册监听器函数,在子进程生命周期期间,当特定的事件发生时会调用这些函数。

child_process.exec() 和 child_process.execFile() 函数可以额外指定一个可选的 callback 函数,当子进程结束时会被调用。

child_process.exec(command[, options][, callback])

exec会创建shell去执行命令,执行完成后将stdout、stderr作为参数传入回调方法。

参数解析

  • command <string> 参数使用空格分隔
  • options
    • cwd <string> 子进程的当前工作路径
    • env <object> 环境变量键值对
    • encoding 'utf8'
    • shell <string> 执行的命令,默认为/bin/sh
    • killSignal <string> 默认为SIGTERM
  • callback 进程被终止时调用
    • error Error
    • stdout
    • stderr
const { exec } = require('child_process')
// 列出当前目录下的文件或目录
const subProcess = exec('ls', (error, stdout, stderr) => {
   if (error) {
     console.log(error.code)    // 子进程退出码exitCode
     console.log(error.signal)  // 退出信号
   }
  console.log('stdout: ' + stdout)
  console.log('stderr: ' + typeof stderr)
})
// 在process那章中的信号事件列表,监听 SIGTERM 和 SIGINT可以阻止默认的行为
subProcess.on('SIGTERM', () => {
  console.log('signal')
})

child_process.execFile(file[, args][, options][, callback])

多了一个参数args <string[]> 是一个数组,元素都是字符串

在mac上,一般使用execFile来代替exec,因为他不需要创建shell来运行命令

在windows上是不能直接使用这个方法的,必须使用execspawn来代替,并将.bat.cmd作为参数传入

// 以buffer的形式输出npm的版本号
const subProcess = execFile('npm', ['-v'], {
  encoding: 'buffer'
})

subProcess.stdout.on('data', chunk => {
  console.log(chunk)    // <Buffer 36 2e 34 2e 31 0a>
})

// String.fromCodePoint(0x36) "6"

例如可以手动执行shell文件或nodejs文件

创建.bin文件,声明执行脚本#!/usr/bin/env node

#!/usr/bin/env node

console.log('this is node env')

execFileoptions参数没有shell选项

const subProcess = execFile('./.bin', {
  encoding: 'utf8'
}, err => {
  err && console.log(err)
})

注意,我们会发现没有权限执行,献给文件赋上可执行权限

chomd +x ./.bin

child_process.fork(modulePath[, args][, options])

child_process.fork() 方法是 child_process.spawn() 的一个特殊情况,专门用于衍生新的 Node.js 进程。 跟 child_process.spawn() 一样返回一个 ChildProcess 对象。 返回的 ChildProcess 会有一个额外的内置的通信通道,它允许消息在父进程和子进程之间来回传递。 详见 subprocess.send()。

  • modulePath <string> 子进程运行的文件路径
  • args <array>
  • options
    • execPath 用来创建子进程的可执行文件,默认是/usr/local/bin/node,因为默认是用的父进程的execPath
    • execArgv 要传给执行路径的字符参数列表,默认为process.execArgv
    • silent 默认是false,即子进程的stdio从父进程继承。如果是true,则直接pipe向子进程的child.stdin、child.stdout等。
    • stdio 如果声明了stdio,则会覆盖silent选项的设置。

下面用例子来说明以上参数

// 例子1,execPath
const subProcess = fork('./.bin', {
  execPath: '/bin/sh',
  silent: true
})

subProcess.stdout.on('data', chunk => {
  console.log(chunk)
})
// 例子2,silent
const subProcess = fork('./child.js', {
  // 表示stdin、 stdout 和 stderr 会被导流到父进程中
  silent: true
})
// 在父进程中定义的标准输出
subProcess.stdout.setEncoding('utf8')
subProcess.stdout.on('data', chunk => {
  console.log('chunk data: ', chunk)
})

// console.log('chunk data: this is child')
// 如果设置为silent: false,那么子进程中将不存在subProcess.stdout,输出将在子进程中完成

stdio参数在spawn方法时说明

child_process.spawn(command[, args][, options])

上面的三个方法都是基于此的,所以我们可以用这个方法为所欲为了

  • command
  • args <Array>
  • options
    • argv0
    • stdio
    • detached
    • shell <boolean> | <string> ,默认为false,表示没有shell。设置为true或可执行路径,表示在shell上运行。

1. options.stdio

用于配置子进程与父进程之间建立的管道,取值如下:

  • ‘pipe’ - 等同于 [‘pipe’, ‘pipe’, ‘pipe’] (默认)
  • ‘ignore’ - 等同于 [‘ignore’, ‘ignore’, ‘ignore’]
  • ‘inherit’ - 等同于 [process.stdin, process.stdout, process.stderr] 或 [0,1,2]

默认情况下,子进程的 stdin、 stdout 和 stderr 会重定向到 ChildProcess 对象上相应的 subprocess.stdin、 subprocess.stdout 和 subprocess.stderr 流。 这等同于将 options.stdio 设为 [‘pipe’, ‘pipe’, ‘pipe’]。

例子1,验证stdio: 'inherit'

依然是.bin文件

echo 'sh env from child'

child.js内容

const ls = spawn('sh', ['./.bin'], {
  stdio: 'inherit'
})
// 此时管道没有被指向到父进程,所以ls.stdout为空
// 输出'sh env from child'

例子2,验证stdio: 'pipe' 其实也就是默认的

const ls = spawn('sh', ['./.bin'], {
  stdio: 'inherit'
})

ls.stdout.on('data', (data) => {
  console.log(`stdout: ${data}`)
})
// 输出 'stdout: sh env from child'

建立IPC

子进程通过fork()exec()创建时,父子进程之间会建立IPC通道,此时可以利用subProcess.send发送消息到子进程,当子进程是node进程时,可以用'message'事件接受消息

subProcess.send(message[, sendHandle[, options]][, callback])

  • message <object> 键值对形式的消息,例如{ name: 'ym' }
  • sendHandle <Handle> 一般用来发送创建server对象
  • options <Object>
  • callback <Function>

返回<boolean>

例子:

// parent.js
const { fork } = require('child_process')
const ls = fork('./child2.js')

ls.on('message', m => {
  console.log('父进程收到消息', m)
})

ls.send({ name: 'ym' })
// child.js
process.on('message', m => {
  console.log('子进程受到消息', m)
})

process.send({ name: 'cjh' })

输出

子进程受到消息 { name: 'ym' }
父进程收到消息 { name: 'cjh' }

同步方法

大部分时候,子进程的创建是异步的。也就是说,它不会阻塞当前的事件循环,这对于性能的提升很有帮助。

当然,有的时候,同步的方式会更方便(阻塞事件循环),比如通过子进程的方式来执行shell脚本时。

  • spawnSync()
  • execSync()
  • execFileSync()

Author: Juntech
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source Juntech !
评论
  TOC