Node.js Async Hooks中run函数未调用next致Nodemon崩溃
解决Node.js Express中Async Local Storage搭配Nodemon崩溃的问题
问题场景
开发Node.js Express应用时,使用Async Local Storage实现请求ID中间件,相关代码如下:
Async Local Storage文件(asyncContext.ts)
import { AsyncLocalStorage } from 'async_hooks'; const asyncContext = new AsyncLocalStorage<Map<string, string>>(); export default asyncContext;
请求处理中间件
import { Response, NextFunction } from 'express'; import { customAlphabet } from 'nanoid/async'; import { Request } from '../../interfaces/Request'; import asyncLocalStorage from '../asyncContext'; export const requestIdHandler = async (req: Request, res: Response, next: NextFunction) => { req.id = await customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz', 20)(); const store = new Map<string, string>(); asyncLocalStorage.run(store, () => { store.set('requestId', req.id); res.setHeader('X-Request-Id', req.id); next(); }); };
触发任意API时,应用出现Nodemon崩溃错误:
[nodemon] app crashed - waiting for file changes before starting...
移除asyncLocalStorage.run函数后应用恢复正常,且不使用Nodemon直接运行时也能正常工作。
问题原因
Nodemon的热重载机制与Node.js的async_hooks模块(Async Local Storage基于此实现)存在兼容性冲突:
async_hooks会追踪所有异步资源的生命周期,Nodemon在应用重启时,无法彻底清理这些异步资源的引用,导致内存泄漏或资源状态异常,最终触发崩溃。- 异步中间件内嵌套调用
asyncLocalStorage.run,会导致异步上下文的传递逻辑与Nodemon的重启流程产生冲突,加剧了崩溃概率。
解决方案
方法1:改用同步生成请求ID,简化中间件逻辑
nanoid提供同步版本的customAlphabet,将中间件改为同步函数,避免异步上下文与Nodemon的冲突:
import { Response, NextFunction } from 'express'; import { customAlphabet } from 'nanoid'; // 引入同步版本 import { Request } from '../../interfaces/Request'; import asyncLocalStorage from '../asyncContext'; export const requestIdHandler = (req: Request, res: Response, next: NextFunction) => { const generateId = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz', 20); req.id = generateId(); // 同步生成请求ID const store = new Map<string, string>(); asyncLocalStorage.run(store, () => { store.set('requestId', req.id); res.setHeader('X-Request-Id', req.id); next(); }); };
方法2:调整Nodemon配置,优化重启逻辑
在项目根目录创建nodemon.json,添加延迟重启、明确监听范围的配置,确保异步资源有足够时间清理:
{ "ignore": ["node_modules/", ".git/", "dist/"], "watch": ["src/"], "execMap": { "ts": "ts-node" }, "env": { "NODE_ENV": "development" }, "delay": 1000 // 延迟1秒重启,等待异步资源清理完成 }
开发时也可临时禁用async_hooks的追踪(仅用于开发环境,禁止生产使用):
NODE_DISABLE_ASYNC_HOOKS=1 nodemon src/index.ts
方法3:替换为cls-hooked库
如果上述方案无效,可改用兼容性更好的cls-hooked库实现异步上下文管理:
- 安装依赖:
npm install cls-hooked
- 重构上下文文件(asyncContext.ts):
import { createNamespace } from 'cls-hooked'; const asyncContext = createNamespace('request-context'); export default asyncContext;
- 调整中间件:
import { Response, NextFunction } from 'express'; import { customAlphabet } from 'nanoid/async'; import { Request } from '../../interfaces/Request'; import asyncContext from '../asyncContext'; export const requestIdHandler = async (req: Request, res: Response, next: NextFunction) => { req.id = await customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz', 20)(); asyncContext.run(async () => { asyncContext.set('requestId', req.id); res.setHeader('X-Request-Id', req.id); next(); }); };
内容的提问来源于stack exchange,提问作者Yash
相关产品推荐
相关产品推荐

