在自研脚手架阶段,对node的同步/异步执行进行了应用;然而未能深层次去理解为何这么设计,这次也是通过问题的形式来进行了一些思考。
⚠️注: 目前是12版本的node;你可能当初用的版本是16版以上用TS来写的;然而node的基本上的思路是没有太多变动的。
思考
问题一:exec和execFile到底什么区别?问题二: 为什么exec/execFile/fork都是通用spawn实现的,spawn的作用到底是什么?问题三:为什么spawn没有回调;exec和execFile可能回调?问题四:为什么spawn调用后,需手动调用child(spawn返回值).stdout.on('data',callback);spawn.stdout和spawn.stderr到底是什么?问题五:为什么有data/errer/exit这么多种回调;他们的执行程序到底是什么?
一、 源码剖析
源码分析方法:依据办法的执行调用程序来进行剖析源码
exec源码剖析
源码目录构造
child_process.js
- exec
- execFile
- spawn
internal/child_process.js
- ChildProcess
- spawn
代码执行流程
执行本地代码
首先在本地创立index.js;
const cp = require('child-process'); const child = cp.exec('ls -la|grep node_modules', function(err, stdout stderr){ console.log(err, stdout, stderr); });
执行 cp.exec办法
将会调用node的内置库【child_process】中的【exec】办法,进行参数的标准化解决【normalizeExecArgs】
function exec (command, options, callback) { const opts = noramlizeExeArgs(command, options, callback); return module.exports.execFile(opts.file, opts.optionns, opts.callback);}
入参解决:
opts:{file : "ls -al|grep node_modules",options : {shell : ture},callback : .....,}
入参解决后的后果:
- file 是输出的命令
- options 增加了shell为true
- callback 没有变动
返回后果:
return module.exports.execFile
- 间接调用execFile办法
通过exec办法中的noramilizeExecArgs办法将参数转化成execFile办法的参数一样;
进入execFile办法
a. 首先还是会进行一个参数的标准化解决【normalizeExecFileArgs】
b. 调用spawn child办法,次要目标是创立一个子过程并且对它进行异步执行
const child = spawn (file, args, { cwd: options.cwd, env: options.env, gid: options.gid, uid: options.uid, shell: options.shell, .....});
spawn参数阐明:
- file: "ls -al|grep node_modules",
- agrs: 没有参数,
- object:{shell: true}; 中只有shell参数显示true,须要用外部的shell脚本去执行.
调用spawn办法
a. 进行参数的标准化解决【normalizeExecFileArgs】
b. 调用 child = new ChildProcess()
function spawn(file, args, options) { const opts = normalizeSpawnArguments(file, args, options); const child = new ChildProcess();// 在child,创立的子过程当中有一个_handle是理论的过程,_handle= Process{onexit:Function,...};调用的形式是须要用spwan来调用。spwan最终执行的是_handle的spwan;};
opts返回的参数解决:
file: '/bin/sh'
//这个是shell的主命令
在本地执行下:
shell的应用
办法一: 间接执行shell文件/bin/sh test.shell
办法二: 间接执行shell语句
/bin/sh -c "ls-al|grep node_modules"
因为传入了参数: shell = true
;所以file就设置为/bin/sh
;示意用shell主命令来执行。
args: ["bin/sh", "-c", "ls -al|grep node_modules"]options: {cwd: null,...shell: true,...}envPairs: // 操作系统的环境变量数据
new ChildProcess
a. 起源是node的外部库【interinternal/child_process】,node的内置库才能够调用到。
b. 这个外部库中有一个ChildProcess类;对应的就是子过程类,(就是子过程);所以在spawn中new ChildProcess()
就创立了一个子过程类;
child_process.js
const child_process = require('internal/child_process') const { ... ChildProcess, ... } = child_process;
- interinternal/child_process库中的ChildProcess
internal/child_process.js
const {Process} = internalBinding('process_warp');// 引入c++文件 this._handle = Process;
创立子过程之后,调用spawn办法,
child.spwan
;利用子过程去执行命令,执行完之后,间接返回,return child
;function spawn(file, args, options) { const opts = normalizeSpawnArguments(file, args, options); const child = new ChildProcess();...child.spawn({ file: opts.file, // "/bin/sh" args: opts.args, // ["/bin/sh", "-c", "ls -a | grep node_modules"] cwd: options.cwd, ...}) return child;
child.spawn是把命令执行起来的办法,位于
internal/child_process
文件中,最外围的是this._handle.spawn
办法,之前只是创立了过程对象,没有调配任何理论资源。调用this._handle.spawn
过程就被执行起来。相应也会生成子过程ID;spawn执行完之后,回到execFile中,进行往下执行
a. 对输入流进行监听:
child.stdout.on('data')
b. 对谬误流进行监听:child.stderr.on('data')
....if (child.stdout) { if(encoding) { child.stdout.setEncoding(encoding);child,stdout.on('data', function onChildStdout(chunk){ const encoding = child.stdout.readableEncoding; ... } }}...if (child.stderr) { if(encoding) { child.stderr.setEncoding(encoding);child,stdout.on('data', function onChildStderr(chunk){ const encoding = child.stderr.readableEncoding; ... } }}...child.addListener('close', exithandler);child.addListener('error', errorhander);return chuild;
这也是为什么execFile能够返回callback的起因;手动做了监听;
c. 对过程进行exit
和error
的监听;
总结
以上是exec办法执行流程的剖析,如图: