node中child_process的回调执行流程的源码分析

回调打印结果
正确的代码执行流程

const cp = require('child_process'); const path = require('path'); const child = cp.exec('ls -al|grep node_modules', function(err, stdout,stderr){ console.log('start------------------'); console.log(err, stdout, stderr); console.log('end------------------'); }) child.stdout.on('data', (chunk) => { console.log('stdout data ='https://www.it610.com/article/, chunk); }); child.stderr.on('data', (err) => { console.log('stderr data ='https://www.it610.com/article/, err ); }); child.stdin.on('close', ()=>{ console.log('stdin close'); }); child.stdout.on('close', () => { console.log('stdout close'); }); child.stderr.on('close', () => { console.log('stderr close'); }) child.on('exit',(exitCode) => { console.log('exit=', exitCode ); }) child.on('close',() => { console.log('close!'); })

代码执行的打印顺序
stdout data = https://www.it610.com/article/drwxr-xr-x280 chensistaff89607 21 17:08 node_modules exit= 0 stdin close stderr close start------------------ null drwxr-xr-x280 chensistaff89607 21 17:08 node_modules end------------------ close! stdout close

错误的代码执行流程
ls -al|grep node_modules这个命令修改为错误的命令去执行,来观察回调的执行结果,ls3333333 -al|grep node_modules
const child = cp.exec('ls3333333 -al|grep node_modules', function(err, stdout,stderr){ console.log('start------------------'); console.log(err, stdout, stderr); console.log('end------------------'); }) child.stdout.on('data', (chunk) => { console.log('stdout data ='https://www.it610.com/article/, chunk); }); child.stderr.on('data', (err) => { console.log('stderr data ='https://www.it610.com/article/, err ); }); child.stdin.on('close', ()=>{ console.log('stdin close'); }); child.stdout.on('close', () => { console.log('stdout close'); }); child.stderr.on('close', () => { console.log('stderr close'); }) child.on('exit',(exitCode) => { console.log('exit=', exitCode ); }) child.on('close',() => { console.log('close!'); })

stderr data = https://www.it610.com/bin/sh: ls3333333: command not found exit= 1 stdin close stdout close start------------------ Error: Command failed: ls3333333 -al|grep node_modules /bin/sh: ls3333333: command not foundat ChildProcess.exithandler (node:child_process:398:12) at ChildProcess.emit (node:events:539:35) at maybeClose (node:internal/child_process:1092:16) at Socket. (node:internal/child_process:451:11) at Socket.emit (node:events:539:35) at Pipe. (node:net:709:12) { code: 1, killed: false, signal: null, cmd:'ls3333333 -al|grep node_modules' }/bin/sh: ls3333333: command not foundend------------------ close! stderr close

【node中child_process的回调执行流程的源码分析】从打印结果可以看出,正确的输出结果和错误的输出结果,在打印上还是存在差异,为此我研究了源码,为了看上去不太枯燥,我将尽可能的用通俗易懂的方式来进行描述,当然如果觉得还是看源码比较清晰一点,也可以对照node源码来看,或者看我上两篇文章;我这里使用的是node v12版本;执行原理和目前的node版本没有差别;我手绘了一张回调函数的执行流程图;
解答:
  • 为什么打印顺序会是这样的结果?
  • 错误的执行打印顺序又为何是这样的?
执行流程
node中child_process的回调执行流程的源码分析
文章图片

描述
  • Process执行命令:子进程执行命令是通过this._hanlde.spawn方法实现的
  • 执行C++的代码后,会返回是否存在异常的一个code码;返回0表示成功,小于0表示失败;
  • 成功会进到onStreamRead方法,这个方法主要是来监听子进程往流填写信息,同时会派发一个data事件;child.stdout.on('data',()=>{}), child.stderr.on('data',()=>{})
  • 当流中的信息填写完成后,认为流已经结束了。会关闭回调方法onReadableStreamEnd,对所有的socket进行广播一个close事件;只有socket可以监听到child.stdout.on(close',()=>{}), child.stderr.on('close',()=>{})
  • 接下来会进行一系列的关闭操作
  • Process会执行关闭流程,回调子进程中的onexit方法,Process.onexit,派发一个exit事件,表示子进程已经执行完成,进程退出;child.on('exit',() => {})
  • 最后Socket all close 所有的Scoket端口都关闭。派发一个close事件,child.on('close',() => {})
因为进程是异步执行的所有两条线
Process的进程执行流程
  1. 执行完成之后就会执行Process.onexit 关闭进程
流的读取线
  1. Process进程执行完后,在pipe管道中进行信息的写入; onStreamRead,一共三个流,分别是输入流、输出流、错误流,会循环调用onStreamRead函数,并且emit data事件;
  2. 所有的信息填写完成之后,调用onReadableStreamEnd,onReadableStreamEnd 会调用destroy方法进行关闭流程,将当前的pipe和Socket进行关闭,socket关闭以后会调用 emit close事件,所有的socket emit colose事件都会监听到以后,会调用mybeClose;
  3. 当mybClose判断所有的Socket(Socket all close)所有的流关闭完以后,派发close事件;
    流读取错误: 当监听到异常的时候,还会emit err事件,因为流没有读取所有直接关闭,onStreamReadableEnd
总结
data/error/close/exit的区别
  • data:主进程读取数据过程中 通过onStreamRead 去派发的data事件。
  • error:命令执行失败后发起的回调。
  • close:子进程所有的socket通信端口全部关闭之后发起的回调。
  • exit:子进程关闭完成后发起的回调。
  • stdout close/ stderr close :特定的PIPE读取完成之后执行onReadableStreamEnd关闭Stock是发起的回调。

    推荐阅读