Node.js的异步非阻塞特性让它在处理高并发场景时表现优异,但异步资源的生命周期分散在不同阶段,排查异步相关的问题时往往难以定位资源的状态变化。async_hooks模块就是Node.js提供的用于跟踪异步资源生命周期的内置模块,能够监听异步资源从创建到销毁的全流程事件。

async_hooks核心概念
async_hooks模块的核心是AsyncHook类,它通过监听异步资源的几个关键生命周期事件来工作,每个事件对应一个回调函数:
- init:异步资源被创建时触发,会传入资源的唯一ID、资源类型、触发该资源的父异步资源ID等信息
- before:异步资源的回调即将执行前触发
- after:异步资源的回调执行完成后触发
- destroy:异步资源被销毁时触发
- promiseResolve:Promise被resolve时触发,仅针对Promise类型的异步资源
基础使用示例
首先需要通过require引入async_hooks模块,然后创建钩子实例并启用,才能开始监听异步资源事件。下面是一个基础的跟踪示例:
const async_hooks = require('async_hooks');
// 存储异步资源的生命周期日志
const asyncResourceLog = new Map();
// 创建异步钩子实例
const hook = async_hooks.createHook({
init(asyncId, type, triggerAsyncId, resource) {
// 记录异步资源初始化信息
asyncResourceLog.set(asyncId, {
type,
triggerAsyncId,
initTime: Date.now(),
status: 'init'
});
console.log(`异步资源初始化: id=${asyncId}, 类型=${type}, 父资源id=${triggerAsyncId}`);
},
before(asyncId) {
const log = asyncResourceLog.get(asyncId);
if (log) {
log.status = 'before_callback';
log.beforeTime = Date.now();
}
console.log(`异步资源回调即将执行: id=${asyncId}`);
},
after(asyncId) {
const log = asyncResourceLog.get(asyncId);
if (log) {
log.status = 'after_callback';
log.afterTime = Date.now();
}
console.log(`异步资源回调执行完成: id=${asyncId}`);
},
destroy(asyncId) {
const log = asyncResourceLog.get(asyncId);
if (log) {
log.status = 'destroyed';
log.destroyTime = Date.now();
}
console.log(`异步资源销毁: id=${asyncId}`);
},
promiseResolve(asyncId) {
console.log(`Promise资源resolve: id=${asyncId}`);
}
});
// 启用钩子
hook.enable();
// 测试异步操作:定时器
setTimeout(() => {
console.log('定时器回调执行');
}, 100);
// 测试异步操作:Promise
Promise.resolve().then(() => {
console.log('Promise回调执行');
});
// 运行一段时间后禁用钩子,避免影响后续逻辑
setTimeout(() => {
hook.disable();
console.log('异步资源生命周期日志:', asyncResourceLog);
}, 200);
代码执行流程说明
上述代码中,我们首先定义了一个Map用来存储每个异步资源的生命周期信息,然后创建了包含各个生命周期回调的钩子实例。启用钩子后,执行定时器和Promise两个异步操作,钩子会自动监听这些异步资源的各个阶段:
- 定时器和Promise创建时,init回调会触发,记录资源的类型和触发它的父资源ID
- 定时器和Promise的回调执行前,before回调触发
- 回调执行完成后,after回调触发
- 定时器资源销毁时,destroy回调触发;Promise被resolve时,promiseResolve回调触发
实际应用场景
async_hooks的异步资源跟踪能力可以应用在多个实际场景中:
- 异步上下文传递:比如实现请求级别的日志追踪,将同一个请求下的所有异步操作关联到同一个请求ID,方便排查问题
- 性能分析:统计每个异步资源的创建到销毁的耗时,找出耗时过长的异步操作,优化性能
- 资源泄漏排查:跟踪异步资源的销毁情况,找出没有被正常销毁的异步资源,定位内存泄漏问题
使用注意事项
虽然async_hooks功能强大,但使用时需要注意以下几点:
- async_hooks会对所有异步操作进行监听,会带来一定的性能开销,生产环境如果不是必要场景不建议长期开启
- init回调中不要执行复杂的逻辑,避免影响异步资源的正常创建流程
- 禁用钩子时调用disable方法即可,不需要手动销毁钩子实例
- 部分Node.js版本中,某些内置的异步资源类型可能不会被监听,使用时需要测试对应版本的兼容性
异步上下文传递示例
下面是一个简单的请求上下文传递示例,利用async_hooks跟踪同一个异步链路下的所有操作:
const async_hooks = require('async_hooks');
// 存储每个异步资源对应的请求上下文
const asyncContext = new Map();
// 当前正在执行的异步资源ID对应的上下文
let currentContext = null;
const hook = async_hooks.createHook({
init(asyncId, type, triggerAsyncId) {
// 如果父资源有上下文,就把上下文传递给当前资源
if (asyncContext.has(triggerAsyncId)) {
asyncContext.set(asyncId, asyncContext.get(triggerAsyncId));
}
},
before(asyncId) {
// 回调执行前,将当前上下文设置为该资源对应的上下文
currentContext = asyncContext.get(asyncId) || null;
},
after(asyncId) {
// 回调执行后,清空当前上下文
currentContext = null;
},
destroy(asyncId) {
// 资源销毁时,删除对应的上下文
asyncContext.delete(asyncId);
}
});
hook.enable();
// 模拟一个请求处理流程
function handleRequest(requestId) {
// 初始化请求上下文,关联到当前执行的异步资源
const currentAsyncId = async_hooks.executionAsyncId();
asyncContext.set(currentAsyncId, { requestId });
// 异步操作中可以获取到对应的请求上下文
setTimeout(() => {
console.log(`处理请求: ${currentContext.requestId}`);
// 嵌套的异步操作也能获取到上下文
Promise.resolve().then(() => {
console.log(`嵌套处理请求: ${currentContext.requestId}`);
});
}, 50);
}
handleRequest('req_123');
setTimeout(() => {
hook.disable();
}, 200);
这个示例中,我们通过async_hooks将请求ID关联到整个异步链路的所有资源上,即使是在嵌套的异步操作中,也能获取到对应的请求上下文,实现了异步场景下的上下文透传。
Node.jsasync_hooks异步资源生命周期跟踪修改时间:2026-07-22 02:42:31