前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
  • 基础篇

    • 环境搭建
    • node部署
    • 基础应用
  • 内置模块

    • 1.0 本地路径处理 path
    • 2.0 文件系统操作 fs
    • 3.1 基础调试 console
    • 3.2 本地调试远程服务器上的Node代码
    • 4.1 网络服务 http
    • 4.2 网络服务 http res
    • 4.3 网络服务 http req
    • 4.4 网络服务 http server
    • 4.5 网络服务 http client
    • 4.6 网络服务 https
    • 4.7 网络TCP net
    • 4.8 网络UDP dgram
    • 4.9 域名解析 dns
    • 5.0 网络地址解析 url
    • 5.1 URL查询字符串 querystring
    • 6.1 流操作 stream
    • 6.2 逐行读取 readline
    • 7.1 进程相关 process
    • 7.2 子进程 child
    • 8.1 二进制数据 buffer
    • 8.2 二进制解码 string_decoder
    • 9.1 事件机制 events
    • 9.2 实用工具模块 util
    • 9.3 数据加密 crypto
    • 9.4 MD5入门介绍及crypto模块的应用
    • 9.4 资源压缩 zlib
    • 9.5 集群 cluster
    • 9.6 v8
  • 进阶篇

    • 5分钟入门非对称加密用法
    • cluster
    • cookie_parser深入
    • crypto模块之理论篇
    • express+cookie_parser签名机制深入剖析
    • express+session实现简易身份认证
    • https
    • log4js入门实例
    • node8_napi
    • 使用 async 控制并发
    • 使用 eventproxy 控制并发
    • 使用 superagent 与 cheerio 完成简单爬虫
    • 基于express+muter的文件上传
    • 将图片转成datauri嵌入到html
    • 常用中间件 body_parser 实现解析
    • 日志模块morgan
    • 服务端字符编解码&乱码处理 charset_enc_dec
    • 测试用例mocha,should,istanbul
    • 调试日志打印debug模块
  • Express

    • 1.1 Express概览篇
    • 1.2 Express
  • Koa2

    • 1.0 koa2概览篇
    • 1.1 快速开始
    • 1.2 async await使用
    • 1.3 koa2简析结构
    • 1.4 koa中间件开发与使用
    • 10.1 单元测试
    • 11.1 开发debug
    • 12.1 快速启动
    • 12.2 框架设计
    • 12.3 分层操作
    • 12.4 数据库设计
    • 12.5 路由设计
    • 12.6 webpack4环境搭建
    • 12.7 使用react
    • 12.8 登录注册功能实现
    • 12.9 session登录态判断处理
    • 13.1 import export使用
    • 2.1 原生koa2实现路由
    • 2.2 koa router中间
    • 3.1 GET请求数据获取
    • 3.2 POST请求数据获取
    • 3.3 koa bodyparser中间件
    • 4.1 原生koa2实现静态资源服务器
    • 4.2 koa static中间件
    • 5.1 koa2使用cookie
    • 5.2 koa2实现session
    • 6.1 koa2加载模板引擎
    • 6.2 ejs模板引擎
    • 7.1 busboy模块
    • 7.2 上传文件简单实现
    • 7.3 异步上传图片实现
    • 8.1 mysql模块
    • 8.2 async await封装使用mysql
    • 8.3 项目建表初始化
    • 9.1 原生koa2实现JSONP
    • 9.2 koa jsonp中间件
  • 其他

    • 操作数据库
    • Session 与 Token
    • Cookie、Session、Token、JWT
    • Socket

# 子进程 child

30 秒速记

  • child_process 让 Node 去跑别的程序:shell 命令、二进制、另一个 Node 脚本
  • 四个入口:spawn(流式)、exec(走 shell、缓冲输出)、execFile(不走 shell)、fork(Node 子进程 + IPC)
  • 有外部输入就用参数数组 + 不开 shell,别拼命令字符串,否则就是命令注入
  • 成功的标准是退出码 0 加上产物存在,光「启动了」不算;error、stderr、close、超时都要处理
  • 父进程退出时子进程不一定跟着退,后台任务要想清楚谁来回收

child_process 就是让 Node 去调用别的程序,四个 API 的区别在于走不走 shell、输出是流还是一次性给。 需要实时拿输出、输出量大,用 spawn;跑一条短命令要完整结果,用 exec 或 execFile;要跑另一个 Node 脚本还要互相发消息,用 fork。实际项目里最常见的两个坑:一是把用户输入拼进 exec 的命令字符串,等于开了命令注入的口子;二是只管启动不管结果,命令不存在、中途失败、超时卡住,都被当成成功返回了。

理解「子进程 child」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 process 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

面试官追问

追问 1「生成报告」接口启动子进程后马上返回成功,偶尔报告不存在,问题在哪?
参考回答

接口在子进程结束前就返回了,失败的情况根本没人看。要么等 close 拿到退出码 0 再返回成功,要么接口只返回「已受理」和任务 id,前端轮询任务状态。

追问 2子进程卡住不退,接口一直挂着,怎么兜底?
参考回答

一定要加超时。exec 和 execFile 自带 timeout 选项,spawn 可以传 AbortSignal:

追问 3把子进程调用包成 Promise,最少要处理哪几个事件?
参考回答

error(启动失败,比如命令不存在)、close(拿退出码和信号),再收集一下 stderr 用于报错信息。close 里 code !== 0 就 reject,把 stderr 带上。

追问 4只是读个目录、压缩个文件,有必要起子进程吗?
参考回答

一般没必要。fs、zlib 能做的就别调 ls、gzip,省掉平台差异和 PATH 问题,Windows 上也能跑。子进程适合调真正只有外部程序才有的能力,比如 ffmpeg、git。

追问 5本地好好的,CI 上子进程报 ENOENT,先查什么?
参考回答

ENOENT 基本就是命令找不到。先看 CI 镜像里有没有装这个程序,再看 PATH 和 cwd 对不对。命令依赖 shell 别名或者 nvm 初始化的,在非交互环境里都会失效。

# 模块概览

30 秒速记

  • child_process = Node 调外部程序、把重活交给别的进程的入口,CLI 工具、构建脚本离不开它
  • 最基本的 spawn('ls', ['-lh', '/usr']):命令和参数分开传,返回一个 ChildProcess
  • 输出走流:child.stdout、child.stderr 的 data 事件,一块一块到,不保证按行
  • 结束看 close 的 code:0 成功,非 0 失败,被信号杀掉时 code 是 null、signal 有值
  • 收到过 stdout 不代表成功,最后要以退出码为准

child_process 是 Node 调用外部程序的模块,最基础的用法就是 spawn 起一个进程,监听它的输出和退出。 拿 spawn('ls', ['-lh', '/usr']) 来说,命令和参数分开传,输出通过 stdout、stderr 两个流一块块推过来,进程结束时 close 事件给你退出码。这里有两点要记住:data 事件给的是 Buffer 块,不是一整行,要按行处理得自己拼;判断成败只看退出码,前面打了一堆正常输出,最后也可能以非零码失败。

在node中,child_process这个模块非常重要。掌握了它,等于在node的世界开启了一扇新的大门。

举个简单的例子:

const spawn = require('child_process').spawn;
const ls = spawn('ls', ['-lh', '/usr']);

ls.stdout.on('data', (data) => {
  console.log(`stdout: ${data}`);
});

ls.stderr.on('data', (data) => {
  console.log(`stderr: ${data}`);
});

ls.on('close', (code) => {
  console.log(`child process exited with code ${code}`);
});
@前端进阶之旅: 代码已经复制到剪贴板

面试官追问

追问 1stdout 已经收到数据了,能不能直接判定命令成功?
参考回答

不能。很多命令是先输出一部分再失败的,比如编译跑到一半报错。成败要等 close 拿 code,0 才算成功。

追问 2data 回调里按 \n 切分,偶尔一行被切成两半,为什么?
参考回答

data 是按缓冲区大小分块的,不按行。按行处理用 readline 包一下:

追问 3close 回调里 code 是 null,是什么情况?
参考回答

进程是被信号终止的,比如超时被 SIGTERM 杀掉或者被 OOM 干掉,这时第二个参数 signal 有值。日志里要把 code 和 signal 一起打出来。

追问 4线上只记了退出码 2,stderr 是空的,还能查什么?
参考回答

先确认 stderr 监听真的注册了、日志没被截断。有些程序把错误写到 stdout,两路都要收。再用相同参数、相同用户在机器上手动跑一遍,看命令自己的错误码文档里 2 代表什么。

追问 5exit 和 close 事件有什么区别,用哪个?
参考回答

exit 是进程退出,但 stdio 流可能还没读完;close 是进程退出且所有流都关了。要拿完整输出就用 close。

# 几种创建子进程的方式

30 秒速记

  • 四种异步方式:spawn、exec、execFile、fork,每个都有同步版(fork 除外)
  • exec、execFile、fork 底层都是 spawn,差别在默认参数和封装
  • exec = spawn + 开 shell + 缓冲输出 + 回调;execFile = 不开 shell 的 exec;fork = 跑 Node 脚本 + IPC
  • 怎么选:要实时流 → spawn;短命令要完整结果 → execFile;需要管道 / 通配符 → exec;跑 Node 模块要通信 → fork
  • 都是异步的,调用返回只代表进程起来了,结果要在回调或 close 里拿

四个 API 本质上都是 spawn,区别只在于走不走 shell、输出缓不缓冲、带不带 IPC。 exec 帮你开 shell、把输出攒成字符串一次性给回调,方便但有注入和 maxBuffer 风险;execFile 一样给回调,但不开 shell,参数是数组,更安全;fork 专门跑 Node 脚本,自带父子通信通道;spawn 最底层,输出是流,适合长任务。同步版有 spawnSync、execSync、execFileSync,fork 没有同步版,因为它的意义就是长期通信。

注意事项:

  • 下面列出来的都是异步创建子进程的方式,每一种方式都有对应的同步版本。
  • .exec()、.execFile()、.fork()底层都是通过.spawn()实现的。
  • .exec()、execFile()额外提供了回调,当子进程停止的时候执行。

child_process.spawn(command[, args][, options]) child_process.exec(command[, options][, callback]) child_process.execFile(file[, args][, options][, callback]) child_process.fork(modulePath[, args][, options])

面试官追问

  • fork 为什么没有同步版本?

    fork 的价值是父子进程长期跑、互相发消息,同步等它结束就失去意义了。只想同步跑一个 Node 脚本拿结果,用 execFileSync(process.execPath, ['script.js']) 就行。

  • 把 exec(cmd) 改成 execFile,能不能只换函数名?

    不能。execFile 不开 shell,命令字符串得拆成可执行文件和参数数组:exec('git log -1') 要写成 execFile('git', ['log', '-1'])。原来依赖管道、通配符、环境变量展开的,换完就不工作了。

  • exec 用 Promise 写怎么写?

    const { promisify } = require('util');
    const execFile = promisify(require('child_process').execFile);
    const { stdout } = await execFile('git', ['rev-parse', 
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
  • 基础篇

    • 环境搭建
    • node部署
    • 基础应用
  • 内置模块

    • 1.0 本地路径处理 path
    • 2.0 文件系统操作 fs
    • 3.1 基础调试 console
    • 3.2 本地调试远程服务器上的Node代码
    • 4.1 网络服务 http
    • 4.2 网络服务 http res
    • 4.3 网络服务 http req
    • 4.4 网络服务 http server
    • 4.5 网络服务 http client
    • 4.6 网络服务 https
    • 4.7 网络TCP net
    • 4.8 网络UDP dgram
    • 4.9 域名解析 dns
    • 5.0 网络地址解析 url
    • 5.1 URL查询字符串 querystring
    • 6.1 流操作 stream
    • 6.2 逐行读取 readline
    • 7.1 进程相关 process
    • 7.2 子进程 child
      • 模块概览
      • 几种创建子进程的方式
        • child_process.exec(command[, options][, callback])
        • child_process.execFile(file[, args][, options][, callback])
        • child_process.fork(modulePath[, args][, options])
        • child_process.spawn(command[, args][, options])
      • 关于options.stdio
      • 异步 vs 同步
      • 关于options.detached
      • exec()与execFile()之间的区别
      • Class: ChildProcess
      • 非重要的备忘点
      • 相关链接
    • 8.1 二进制数据 buffer
    • 8.2 二进制解码 string_decoder
    • 9.1 事件机制 events
    • 9.2 实用工具模块 util
    • 9.3 数据加密 crypto
    • 9.4 MD5入门介绍及crypto模块的应用
    • 9.4 资源压缩 zlib
    • 9.5 集群 cluster
    • 9.6 v8
  • 进阶篇

    • 5分钟入门非对称加密用法
    • cluster
    • cookie_parser深入
    • crypto模块之理论篇
    • express+cookie_parser签名机制深入剖析
    • express+session实现简易身份认证
    • https
    • log4js入门实例
    • node8_napi
    • 使用 async 控制并发
    • 使用 eventproxy 控制并发
    • 使用 superagent 与 cheerio 完成简单爬虫
    • 基于express+muter的文件上传
    • 将图片转成datauri嵌入到html
    • 常用中间件 body_parser 实现解析
    • 日志模块morgan
    • 服务端字符编解码&乱码处理 charset_enc_dec
    • 测试用例mocha,should,istanbul
    • 调试日志打印debug模块
  • Express

    • 1.1 Express概览篇
    • 1.2 Express
  • Koa2

    • 1.0 koa2概览篇
    • 1.1 快速开始
    • 1.2 async await使用
    • 1.3 koa2简析结构
    • 1.4 koa中间件开发与使用
    • 10.1 单元测试
    • 11.1 开发debug
    • 12.1 快速启动
    • 12.2 框架设计
    • 12.3 分层操作
    • 12.4 数据库设计
    • 12.5 路由设计
    • 12.6 webpack4环境搭建
    • 12.7 使用react
    • 12.8 登录注册功能实现
    • 12.9 session登录态判断处理
    • 13.1 import export使用
    • 2.1 原生koa2实现路由
    • 2.2 koa router中间
    • 3.1 GET请求数据获取
    • 3.2 POST请求数据获取
    • 3.3 koa bodyparser中间件
    • 4.1 原生koa2实现静态资源服务器
    • 4.2 koa static中间件
    • 5.1 koa2使用cookie
    • 5.2 koa2实现session
    • 6.1 koa2加载模板引擎
    • 6.2 ejs模板引擎
    • 7.1 busboy模块
    • 7.2 上传文件简单实现
    • 7.3 异步上传图片实现
    • 8.1 mysql模块
    • 8.2 async await封装使用mysql
    • 8.3 项目建表初始化
    • 9.1 原生koa2实现JSONP
    • 9.2 koa jsonp中间件
  • 其他

    • 操作数据库
    • Session 与 Token
    • Cookie、Session、Token、JWT
    • Socket
完整面试题地址:
作者:程序员poetry
扫码关注作者公众号:「前端进阶之旅」 每天分享技术干货
前端进阶之旅公众号二维码

子进程 child|Nodejs专题系列

# 子进程 child

30 秒速记

  • child_process 让 Node 去跑别的程序:shell 命令、二进制、另一个 Node 脚本
  • 四个入口:spawn(流式)、exec(走 shell、缓冲输出)、execFile(不走 shell)、fork(Node 子进程 + IPC)
  • 有外部输入就用参数数组 + 不开 shell,别拼命令字符串,否则就是命令注入
  • 成功的标准是退出码 0 加上产物存在,光「启动了」不算;error、stderr、close、超时都要处理
  • 父进程退出时子进程不一定跟着退,后台任务要想清楚谁来回收

child_process 就是让 Node 去调用别的程序,四个 API 的区别在于走不走 shell、输出是流还是一次性给。 需要实时拿输出、输出量大,用 spawn;跑一条短命令要完整结果,用 exec 或 execFile;要跑另一个 Node 脚本还要互相发消息,用 fork。实际项目里最常见的两个坑:一是把用户输入拼进 exec 的命令字符串,等于开了命令注入的口子;二是只管启动不管结果,命令不存在、中途失败、超时卡住,都被当成成功返回了。

理解「子进程 child」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 process 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

面试官追问

追问 1「生成报告」接口启动子进程后马上返回成功,偶尔报告不存在,问题在哪?
参考回答

接口在子进程结束前就返回了,失败的情况根本没人看。要么等 close 拿到退出码 0 再返回成功,要么接口只返回「已受理」和任务 id,前端轮询任务状态。

追问 2子进程卡住不退,接口一直挂着,怎么兜底?
参考回答

一定要加超时。exec 和 execFile 自带 timeout 选项,spawn 可以传 AbortSignal:

追问 3把子进程调用包成 Promise,最少要处理哪几个事件?
参考回答

error(启动失败,比如命令不存在)、close(拿退出码和信号),再收集一下 stderr 用于报错信息。close 里 code !== 0 就 reject,把 stderr 带上。

追问 4只是读个目录、压缩个文件,有必要起子进程吗?
参考回答

一般没必要。fs、zlib 能做的就别调 ls、gzip,省掉平台差异和 PATH 问题,Windows 上也能跑。子进程适合调真正只有外部程序才有的能力,比如 ffmpeg、git。

追问 5本地好好的,CI 上子进程报 ENOENT,先查什么?
参考回答

ENOENT 基本就是命令找不到。先看 CI 镜像里有没有装这个程序,再看 PATH 和 cwd 对不对。命令依赖 shell 别名或者 nvm 初始化的,在非交互环境里都会失效。

# 模块概览

30 秒速记

  • child_process = Node 调外部程序、把重活交给别的进程的入口,CLI 工具、构建脚本离不开它
  • 最基本的 spawn('ls', ['-lh', '/usr']):命令和参数分开传,返回一个 ChildProcess
  • 输出走流:child.stdout、child.stderr 的 data 事件,一块一块到,不保证按行
  • 结束看 close 的 code:0 成功,非 0 失败,被信号杀掉时 code 是 null、signal 有值
  • 收到过 stdout 不代表成功,最后要以退出码为准

child_process 是 Node 调用外部程序的模块,最基础的用法就是 spawn 起一个进程,监听它的输出和退出。 拿 spawn('ls', ['-lh', '/usr']) 来说,命令和参数分开传,输出通过 stdout、stderr 两个流一块块推过来,进程结束时 close 事件给你退出码。这里有两点要记住:data 事件给的是 Buffer 块,不是一整行,要按行处理得自己拼;判断成败只看退出码,前面打了一堆正常输出,最后也可能以非零码失败。

在node中,child_process这个模块非常重要。掌握了它,等于在node的世界开启了一扇新的大门。

举个简单的例子:

const spawn = require('child_process').spawn;
const ls = spawn('ls', ['-lh', '/usr']);

ls.stdout.on('data', (data) => {
  console.log(`stdout: ${data}`);
});

ls.stderr.on('data', (data) => {
  console.log(`stderr: ${data}`);
});

ls.on('close', (code) => {
  console.log(`child process exited with code ${code}`);
});
@前端进阶之旅: 代码已经复制到剪贴板

面试官追问

追问 1stdout 已经收到数据了,能不能直接判定命令成功?
参考回答

不能。很多命令是先输出一部分再失败的,比如编译跑到一半报错。成败要等 close 拿 code,0 才算成功。

追问 2data 回调里按 \n 切分,偶尔一行被切成两半,为什么?
参考回答

data 是按缓冲区大小分块的,不按行。按行处理用 readline 包一下:

追问 3close 回调里 code 是 null,是什么情况?
参考回答

进程是被信号终止的,比如超时被 SIGTERM 杀掉或者被 OOM 干掉,这时第二个参数 signal 有值。日志里要把 code 和 signal 一起打出来。

追问 4线上只记了退出码 2,stderr 是空的,还能查什么?
参考回答

先确认 stderr 监听真的注册了、日志没被截断。有些程序把错误写到 stdout,两路都要收。再用相同参数、相同用户在机器上手动跑一遍,看命令自己的错误码文档里 2 代表什么。

追问 5exit 和 close 事件有什么区别,用哪个?
参考回答

exit 是进程退出,但 stdio 流可能还没读完;close 是进程退出且所有流都关了。要拿完整输出就用 close。

# 几种创建子进程的方式

30 秒速记

  • 四种异步方式:spawn、exec、execFile、fork,每个都有同步版(fork 除外)
  • exec、execFile、fork 底层都是 spawn,差别在默认参数和封装
  • exec = spawn + 开 shell + 缓冲输出 + 回调;execFile = 不开 shell 的 exec;fork = 跑 Node 脚本 + IPC
  • 怎么选:要实时流 → spawn;短命令要完整结果 → execFile;需要管道 / 通配符 → exec;跑 Node 模块要通信 → fork
  • 都是异步的,调用返回只代表进程起来了,结果要在回调或 close 里拿

四个 API 本质上都是 spawn,区别只在于走不走 shell、输出缓不缓冲、带不带 IPC。 exec 帮你开 shell、把输出攒成字符串一次性给回调,方便但有注入和 maxBuffer 风险;execFile 一样给回调,但不开 shell,参数是数组,更安全;fork 专门跑 Node 脚本,自带父子通信通道;spawn 最底层,输出是流,适合长任务。同步版有 spawnSync、execSync、execFileSync,fork 没有同步版,因为它的意义就是长期通信。

注意事项:

  • 下面列出来的都是异步创建子进程的方式,每一种方式都有对应的同步版本。
  • .exec()、.execFile()、.fork()底层都是通过.spawn()实现的。
  • .exec()、execFile()额外提供了回调,当子进程停止的时候执行。

child_process.spawn(command[, args][, options]) child_process.exec(command[, options][, callback]) child_process.execFile(file[, args][, options][, callback]) child_process.fork(modulePath[, args][, options])

面试官追问

  • fork 为什么没有同步版本?

    fork 的价值是父子进程长期跑、互相发消息,同步等它结束就失去意义了。只想同步跑一个 Node 脚本拿结果,用 execFileSync(process.execPath, ['script.js']) 就行。

  • 把 exec(cmd) 改成 execFile,能不能只换函数名?

    不能。execFile 不开 shell,命令字符串得拆成可执行文件和参数数组:exec('git log -1') 要写成 execFile('git', ['log', '-1'])。原来依赖管道、通配符、环境变量展开的,换完就不工作了。

  • exec 用 Promise 写怎么写?

    const { promisify } = require('util');
    const execFile = promisify(require('child_process').execFile);
    const { stdout } = await execFile('git', ['rev-parse', 'HEAD']);
    // 非零退出码会 reject,错误对象上有 code、stdout、stderr
    
    @前端进阶之旅: 代码已经复制到剪贴板
  • 调完 spawn 紧接着去读产物文件,发现文件不存在,为什么?

    spawn 返回时子进程才刚启动,命令还没跑完。读产物要放到 close 回调里,并且确认 code === 0。

  • 团队想统一只用 spawn,其他三个都不用,你怎么看?

    底层统一封装没问题,但上层保留语义更清楚:跑 Node 模块就用 fork,它自带 IPC 和 execPath 处理,自己用 spawn 去拼要多写不少代码。我会共用一层超时、日志、退出码处理,入口还是按场景分开。

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

30 秒速记

  • exec(cmd, opts, cb) = 起一个 shell(/bin/sh,Windows 是 cmd.exe)执行整条命令字符串
  • 跑完一次性回调 (error, stdout, stderr),默认是字符串;encoding: 'buffer' 才给 Buffer
  • 失败时 error.code 是退出码,error.signal 是终止信号;有 stderr 不等于失败
  • 输出超过 maxBuffer(v12 起默认 1MB,之前是 200KB)会被杀掉,大输出用 spawn
  • 用户输入拼进命令字符串 = 命令注入,;、&&、$() 都会被 shell 执行

exec 会开一个 shell 跑整条命令,等结束后把 stdout、stderr 一次性交给回调。 因为走 shell,管道、重定向、通配符都能用,写起来跟在终端里敲一样;代价也来自这里,拼进去的任何字符串都会被 shell 解释,用户传个 a.txt; rm -rf ~ 就出事了。另外它把输出全攒在内存里,超过 maxBuffer 子进程会被杀掉。所以我的用法是:固定的短命令、确实需要 shell 语法时用 exec;有外部输入就换 execFile 加参数数组。

← 7.1 进程相关 process8.1 二进制数据 buffer →

fe