← 返回题目列表

Node.js 中 process、环境变量和进程退出应该怎么处理?

高频 中等 第 7 / 27 题 更新于 2026/07/29
Node.jsprocess环境变量优雅退出

简化版

process 提供当前 Node 进程的信息和控制能力,常用来读取环境变量、命令行参数、退出码、运行目录和监听进程事件。工程里要注意环境变量类型都是字符串、不要泄露密钥、不要随意 process.exit(),服务退出时应处理信号、停止接收请求并释放资源。

详细版

常见能力:

  • process.env:读取环境变量。
  • process.argv:读取命令行参数。
  • process.cwd():当前工作目录。
  • process.exitCode:设置退出码,让进程自然退出。
  • SIGTERMSIGINT:处理容器、终端或进程管理器发来的退出信号。

面试重点不是背 API,而是工程边界:配置要校验,密钥不能打印,环境变量要按类型转换;退出时不能粗暴杀进程,要关闭 HTTP server、等待连接排空、关闭数据库连接和定时任务。

完整版教学

一、process 是 Node 进程的控制面

process 是 Node.js 提供的全局进程对象,用来查看和控制当前进程。它本身也是 EventEmitter,因此可以监听退出、异常、信号等事件。

console.log(process.version);
console.log(process.cwd());
console.log(process.argv);
console.log(process.env.NODE_ENV);

这些能力在脚手架、构建工具、BFF 服务、CLI 命令中都非常常见。比如前端工程里的 npm run build -- --mode prod,底层就会涉及命令行参数和环境变量读取。

面试回答这题时,不要只列 API。要讲清楚 process 是运行时与操作系统、启动环境、进程生命周期之间的桥。

二、环境变量读取后必须做类型转换和校验

process.env 中的值本质上都是字符串或未定义。即使你在 shell 里写 PORT=3000,Node 读到的也是字符串 "3000"

const port = Number(process.env.PORT ?? 3000);

if (!Number.isInteger(port) || port <= 0) {
  throw new Error('Invalid PORT');
}

常见错误是把字符串当布尔值:

// 错误示例:ENABLE_CACHE="false" 仍然是真值字符串
if (process.env.ENABLE_CACHE) {
  enableCache();
}

更稳妥的做法是显式解析:

const enableCache = process.env.ENABLE_CACHE === 'true';

如果一个服务有 20 个环境变量,启动时集中校验比运行到某个请求才报错更安全。配置错误应尽早失败,并给出清晰日志。

三、密钥和配置不要混在代码里

数据库密码、JWT 密钥、第三方 API Key 不应该硬编码在仓库里,也不应该出现在前端可见的构建产物中。Node 服务可以通过环境变量、密钥管理系统或部署平台注入配置。

配置类型示例处理方式
普通配置PORTLOG_LEVEL环境变量即可
敏感密钥JWT_SECRET、数据库密码密钥系统或受控环境变量
前端公开变量PUBLIC_API_BASE可以打入产物,但不能放密钥
运行开关ENABLE_CACHE显式类型转换和默认值

假设把 JWT_SECRET 写进仓库,即使后来删除,Git 历史里也可能还能找到。密钥泄露后要轮换,而不只是删除代码。

易错点:前端构建变量一旦进入浏览器产物,就等于公开信息。Node BFF 的服务端环境变量和浏览器环境变量要严格分开。

四、process.argv 适合 CLI,但要做参数解析

process.argv 包含启动命令参数。前两个通常是 Node 可执行文件路径和脚本路径,后面才是用户参数。

// node scripts/create.js --name demo
console.log(process.argv);

简化结构:

[
  "/usr/local/bin/node",
  "/app/scripts/create.js",
  "--name",
  "demo"
]

简单脚本可以手动解析,复杂 CLI 应使用成熟参数解析库,处理短选项、默认值、帮助信息、非法参数和退出码。

工程里还要区分 process.cwd() 和当前文件所在目录。cwd 是命令从哪里执行,文件目录是脚本在哪里。CLI 被不同目录调用时,依赖 cwd 读配置要特别小心。

五、不要滥用 process.exit()

process.exit() 会强制进程退出,可能导致 stdout/stderr 尚未写完、异步日志未刷盘、请求尚未响应、数据库连接未正常关闭。更推荐设置 process.exitCode,让事件循环自然结束。

if (!process.env.JWT_SECRET) {
  console.error('JWT_SECRET is required');
  process.exitCode = 1;
}

如果是 HTTP 服务,不能只是设置退出码,还要停止接收新请求并释放资源。特别是在容器环境中,平台通常会发送 SIGTERM,给应用一段时间优雅退出。

收到 SIGTERM
  -> server.close() 停止接收新连接
  -> 等待进行中请求完成
  -> 关闭 DB / Redis / 定时任务
  -> 设置退出码并退出

如果平台给 30 秒优雅期,你的服务应在 30 秒内完成清理;超过时间会被强杀。

六、异常事件只能兜底,不能当正常控制流

uncaughtExceptionunhandledRejection 常被问到。它们可以用于记录最后错误、触发告警和准备退出,但不应该当作业务错误处理机制。

process.on('uncaughtException', (err) => {
  console.error('fatal', err);
  process.exitCode = 1;
  // 通常应停止接收新请求并退出,由进程管理器拉起
});

process.on('unhandledRejection', (reason) => {
  console.error('unhandled rejection', reason);
});

未捕获异常可能让进程处于未知状态。例如内存数据更新了一半、锁没释放、请求上下文丢失。继续运行不一定安全。生产中更常见做法是记录、排空、退出、由进程管理器重启。

数字例子:如果一个服务有 100 个并发请求,发生未捕获异常后立刻 process.exit(),这 100 个请求可能直接断开;优雅退出则可以停止新流量并给存量请求几秒完成。

七、常见误区与追问

  • 误区:process.env.PORT 是数字。 环境变量读出来是字符串,需要显式转换和校验。
  • 误区:前端变量和服务端变量一样安全。 进入浏览器构建产物的变量就是公开信息,不能放密钥。
  • 误区:出错时直接 process.exit() 最干净。 强退可能丢日志、断请求、未释放资源,应优先优雅退出。
  • 追问:SIGTERM 和 SIGINT 常见来源是什么? SIGTERM 常来自容器或进程管理器终止,SIGINT 常来自终端 Ctrl+C。
  • 追问:uncaughtException 后还能继续跑吗? 不建议作为常态继续运行,应记录、排空并重启,因为进程状态可能已不可靠。
  • 追问:process.cwd() 和 __dirname 有什么区别? cwd 是执行命令的目录,__dirname 是当前模块文件所在目录,ESM 中要用对应替代方案。

八、加强记忆

process 题按“配置、参数、退出”三块记:配置从 env 来但要转类型和保密;参数从 argv 来但要解析和校验;退出靠信号和退出码管理,尽量优雅排空。服务端 Node 的成熟度,很多时候就体现在这些边界处理上。