Node.js 中 process、环境变量和进程退出应该怎么处理?
简化版
process 提供当前 Node 进程的信息和控制能力,常用来读取环境变量、命令行参数、退出码、运行目录和监听进程事件。工程里要注意环境变量类型都是字符串、不要泄露密钥、不要随意 process.exit(),服务退出时应处理信号、停止接收请求并释放资源。
详细版
常见能力:
process.env:读取环境变量。process.argv:读取命令行参数。process.cwd():当前工作目录。process.exitCode:设置退出码,让进程自然退出。SIGTERM、SIGINT:处理容器、终端或进程管理器发来的退出信号。
面试重点不是背 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 服务可以通过环境变量、密钥管理系统或部署平台注入配置。
| 配置类型 | 示例 | 处理方式 |
|---|---|---|
| 普通配置 | PORT、LOG_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 秒内完成清理;超过时间会被强杀。
六、异常事件只能兜底,不能当正常控制流
uncaughtException 和 unhandledRejection 常被问到。它们可以用于记录最后错误、触发告警和准备退出,但不应该当作业务错误处理机制。
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 的成熟度,很多时候就体现在这些边界处理上。