# 进程相关 process
30 秒速记
process是全局对象,代表「当前这个Node进程」:参数、环境变量、工作目录、输入输出、退出、信号、资源占用都在它上面- 退出优先
process.exitCode = 1,让进程自然结束;process.exit()是硬退出,可能截断还没写完的stdout uncaughtException/unhandledRejection只用来记日志然后退出,别吞掉继续跑,交给PM2、容器去重启- 监听了
SIGTERM就接管了默认退出,清理完要自己退出,还要设个超时兜底 - 配置在启动时集中从
process.env/process.argv读一次,业务代码别到处读
process 是 Node.js 里代表当前进程的全局对象,程序和操作系统打交道基本都经过它。 拿参数用 argv,拿配置用 env,读写终端用 stdin / stdout,退出和信号也在这。工程上最容易出问题的是退出:我一般设 process.exitCode 让程序自然退出,而不是到处 process.exit(),后者会直接掐断还没写完的输出。还有部署时容器发 SIGTERM,要监听它做优雅关闭:
process.on('SIGTERM', () => {
server.close(() => process.exit(0)); // 不再接新请求,处理完存量再退
setTimeout(() => process.exit(1), 10000).unref(); // 10 秒还没关完就强退
});
理解「进程相关 process」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。
工程中还要验证退出码、资源清理和异常输入。属于 process 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。
process 上的东西多,按用途记会清楚很多:
- 输入:
process.argv(命令行参数)、process.execArgv(Node自身参数)、process.env(环境变量)、process.stdin。 - 输出:
process.stdout、process.stderr、process.exitCode。 - 环境:
process.cwd()、process.platform、process.version、process.pid。 - 生命周期:
process.exit()、process.on('exit')、process.on('SIGTERM')、process.on('uncaughtException')、process.on('unhandledRejection')。 - 调度:
process.nextTick()。 - 资源:
process.memoryUsage()、process.cpuUsage()、process.uptime()、process.hrtime.bigint()。
有一点常被忽略:exit 事件的回调里只能做同步操作,异步的写日志、发请求不会执行完。要在退出前做异步清理,得在 SIGTERM 或业务结束的地方做,做完再退出。
process.on('exit', (code) => {
console.log('退出码', code); // 会打印
setTimeout(() => console.log('x'), 0); // 永远不会执行
});
process.exitCode = 2;
面试官追问
追问 1命令行工具打印了「完成」,CI 却判它失败,为什么?
CI 看的是退出码,不看打印内容。多半是某条路径设了非零 exitCode,或者有未处理的 Promise 拒绝,Node 15 起默认会让进程以非零码退出。echo $? 一看就知道。
追问 2process.exitCode = 1 和 process.exit(1) 有什么区别?
前者只是记下退出码,等事件循环空了自然退出,日志、文件写入都能完成;后者立刻退出,挂起的异步操作全丢。脚本里除非确实要马上停,否则用前者。
追问 3代码里加了 process.on('uncaughtException'),打个日志就继续跑,可以吗?
不推荐。抛到这里说明某个流程中途崩了,内存里的状态可能已经是乱的,比如锁没释放、连接一半。记完日志就退出,让 PM2 或容器重启一个干净的进程。
追问 4容器发版时总有一批请求报错,和 process 有什么关系?
容器停止时先发 SIGTERM,没处理的话进程立刻退出,正在处理的请求就断了。监听 SIGTERM,先 server.close() 停止接新请求,等存量处理完再退,设个超时防止卡死。
追问 5任务报完错一直不退出,可能是什么原因?
事件循环里还有东西撑着:没清的 setInterval、没关的数据库连接、没关的 readline。可以用 why-is-node-running 这类工具看是哪个句柄,修好清理逻辑,别靠 process.exit() 硬掐。
# 模块概览
30 秒速记
process是全局对象,不用require就能用;require('process')拿到的是同一个对象- 读:命令行参数、环境变量、工作目录、进程
ID、内存占用 - 写:设环境变量、改工作目录、设退出码、改进程标题
- 它是进程边界,不是业务全局变量仓库,别往上挂业务状态
- 配置在启动层集中读一次、校验好,再传给业务模块
process 是 Node.js 的全局对象,用来读写当前进程的信息,不用引入就能直接用。 能读的有命令行参数 argv、环境变量 env、工作目录 cwd()、进程号 pid;能改的有环境变量、工作目录、退出码、进程标题。我的习惯是只在启动入口读 process.env 和 process.argv,整理成一个校验过的 config 对象传下去,业务代码里不直接读,这样测试时换配置很方便,也不会出现十几个地方各自给默认值的情况。
process是node的全局模块,作用比较直观。可以通过它来获得node进程相关的信息,比如运行node程序时的命令行参数。或者设置进程相关信息,比如设置环境变量。
面试官追问
追问 1require('process') 和直接用 process 有区别吗?
没区别,拿到的是同一个对象。显式引入的好处是依赖写得清楚,ESM 里也常写 import process from 'node:process'。
追问 2业务模块里到处 process.env.XXX,有什么问题?
默认值散落各处,同一个配置不同地方可能给不同的默认值;测试要改环境变量才能换配置。启动时集中读、转类型、校验,导出一个 config 对象,业务只用它。
追问 3把用户会话、缓存挂在 process 上当全局变量用,行不行?
别这么干。process 是进程本身的接口,挂上去的东西没有边界、谁都能改,测试也没法隔离。要共享状态就用模块导出的单例。
追问 4服务连到了错误的数据库,代码没报错,怎么用 process 定位?
启动时打一行脱敏后的最终配置及来源,比如数据库主机来自 env 还是 argv 覆盖。重点核对 process.env 里实际拿到的值,密码这类字段只打是否存在,不打明文。
# 环境变量:process.env
30 秒速记
process.env是个对象,存当前进程拿到的环境变量,值永远是字符串NODE_ENV=production node env.js只对这一条命令生效,例子输出应该是「生产环境」- 这种
VAR=x cmd写法Windows的cmd不认,跨平台用cross-env - 数字、布尔要自己转:
process.env.PORT是'3000','false'也是真值 - 密钥通过环境变量注入可以,但别把整个
process.env打进日志
process.env 就是当前进程拿到的环境变量,最常见的用法是读 NODE_ENV 判断是不是生产环境。 执行 NODE_ENV=production node env.js,process.env.NODE_ENV 就是 'production',例子会打印「生产环境」。坑主要有三个:值全是字符串,'false' 也是真值,PORT 要 Number() 转;Windows 的 cmd 不认这种前缀写法,团队里有人用 Windows 就上 cross-env;环境变量是启动时继承来的,在终端里 export 不会影响已经跑起来的进程。
const port = Number(process.env.PORT ?? 3000);
const debug = process.env.DEBUG === 'true'; // 别写 Boolean(process.env.DEBUG)
if (Number.isNaN(port)) throw new Error('PORT 必须是数字'); // 启动时就失败
使用频率很高,node服务运行时,时常会判断当前服务运行的环境,如下所示
if(process.env.NODE_ENV === 'production'){
console.log('生产环境'