如何通过代码关闭带--watch参数启动的Node开发服务器?
问题背景
使用命令 node --env-file=.env --loader ts-node/esm --watch-path=./src index.ts 启动Node进程时,通过setTimeout调用fastify.close()和process.exit(0)后,进程仍处于空闲运行状态。经A/B测试验证,去掉--watch-path参数后进程可正常退出,排除了数据库、服务器连接关闭的问题,推测是--watch-path创建的未处理Promise或活跃文件监听器阻止了进程退出。
原终止逻辑代码:
if (process.argv[1] === import.meta.filename) { // 当前文件为进程入口文件时执行 fastify.listen({port: Number(process.env.PORT), host: process.env.HOST}, (err, address) => { if (err) { fastify.log.error(err) process.exit(1) } console.log(`App listening address: ${address} , on port ${process.env.PORT}`); console.log("node environment: ", process.env.NODE_ENV); (() => { console.log("closing server..."); setTimeout(function () { fastify.close(); process.exit(0); }, 3500) })(); }); }
代码层面解决方案
方案1:等待Fastify关闭完成后强制终止进程
fastify.close()是异步操作,原代码未等待其完成就调用process.exit(),结合--watch-path的监听器残留,容易导致进程无法正常退出。修改为异步等待关闭后,用process.kill()发送终止信号强制清理所有资源:
setTimeout(async function () { console.log("closing server..."); // 等待Fastify服务器完全关闭所有连接 await fastify.close(); // 发送SIGTERM信号,触发Node进程的正常退出流程,自动清理watch监听器 process.kill(process.pid, 'SIGTERM'); }, 3500)
方案2:手动清理watch相关的文件监听器
--watch-path会创建FSWatcher类型的句柄,这类句柄会维持事件循环活跃,阻止进程退出。可通过Node内部API(仅建议开发环境使用)定位并关闭这些监听器:
setTimeout(async function () { console.log("closing server..."); await fastify.close(); // 遍历所有活跃句柄,关闭watch创建的FSWatcher实例 (process as any)._getActiveHandles().forEach((handle: any) => { if (handle.type === 'FSWatcher') { handle.close(); } }); process.exit(0); }, 3500)
注意:
process._getActiveHandles()是Node.js内部非公开API,不保证未来版本兼容,仅适合开发场景临时解决问题。
方案3:解除核心流句柄引用,清空事件循环
通过解除标准输入、输出、错误流的引用,让Node的事件循环失去活跃句柄,确保process.exit()能正常终止进程:
setTimeout(async function () { await fastify.close(); // 解除核心流的引用,允许事件循环清空 process.stdin.unref(); process.stdout.unref(); process.stderr.unref(); process.exit(0); }, 3500)
补充说明
以上方案均针对开发环境的--watch热重载场景,生产环境建议移除watch参数,改用PM2、nodemon等专业进程管理工具实现代码更新后的自动重启。
内容的提问来源于stack exchange,提问作者Vince M

