[Node.js] async_hooks模块
1. 异步资源
node 8.2中的async_hooks
模块,提供了一组API用来跟踪应用中的异步资源(asynchronous resources)。
与异步资源相关的回调函数(callback),
可能会被调用多次,例如net.createServer
的connection
事件,
也可能会被调用一次,例如fs.open
。
异步资源也可能在调用回调函数之前就已经被关闭了。
2. async_hooks用法
const asyncHook = require('async_hooks');
const hook = asyncHooks.createHook({
init(asyncId, type, triggerAsyncId, resource) {
},
before(asyncId) {
},
after(asyncId) {
},
destroy(asyncId) {
}
});
hook.enable();
以上代码创建一个hook
,它可以用来跟踪应用中所有的异步资源,
当资源在被初始化,回调之前,回调之后,销毁后,将自动触发init
,before
,after
,destroy
。
我们可以使用hook.enable();
启用跟踪,还可以使用hook.disable();
来关闭。
其中asyncId
,triggerAsyncId
的介绍见下文。
3. 当心console.log会造成无限循环
我们通常使用的console.log
,向控制台打印消息,
然而,它却是一个异步操作(asynchronous operation),
所以,async_hooks
也可以用来跟踪它。
(参考:Printing in AsyncHooks callbacks)
因此,如果在上述init
,before
,after
,destroy
事件处理函数中出现了console.log
,就会导致无限循环。
我们可以使用fs.writeSync(1, msg)
来代替console.log
,
其中writeSync
函数的第一个参数为文件描述符(file descriptor),
1
表示标准输出(standard output)。
4. 完整的例子
const fs = require('fs');
const asyncHooks = require('async_hooks');
const hook = asyncHooks.createHook({
init(asyncId, type, triggerAsyncId, resource) {
fs.writeSync(1, `init: asyncId-${asyncId},type-${type},triggerAsyncId-${triggerAsyncId}\n`);
},
before(asyncId) {
fs.writeSync(1, `before: asyncId-${asyncId}\n`);
},
after(asyncId) {
fs.writeSync(1, `after: asyncId-${asyncId}\n`);
},
destroy(asyncId) {
fs.writeSync(1, `destroy: asyncId-${asyncId}\n`);
}
});
hook.enable();
console.log('hello');
// hook.disable(); // 注意,这里不要disable,否则只能触发init事件
执行后,输出:
init: asyncId-2, type-TTYWRAP, triggerAsyncId-1
init: asyncId-3, type-SIGNALWRAP, triggerAsyncId-1
init: asyncId-4, type-TTYWRAP, triggerAsyncId-1
hello
init: asyncId-5, type-TickObject, triggerAsyncId-1
before: asyncId-5
after: asyncId-5
destroy: asyncId-5
5. 自定义AsyncResource
async_hooks
模块除了可以跟踪node中内置的异步资源,还可以跟踪自定义的资源,
要做到这一点,我们需要继承AsyncResource
类,然后手动触发事件。
其中,AsyncResource
类,是async_hooks
模块导出对象的一个属性asyncHooks.AsyncResource
。
const fs = require('fs');
const asyncHooks = require('async_hooks');
class MyResource extends asyncHooks.AsyncResource {
constructor() {
super('my-resource');
}
asyncMethod(callback) {
this.emitBefore();
callback();
this.emitAfter();
}
close() {
this.emitDestroy();
}
}
const hook = asyncHooks.createHook({
init(asyncId, type, triggerAsyncId, resource) {
fs.writeSync(1, `init: asyncId-${asyncId}, type-${type}, triggerAsyncId-${triggerAsyncId}\n`);
},
before(asyncId) {
fs.writeSync(1, `before: asyncId-${asyncId}\n`);
},
after(asyncId) {
fs.writeSync(1, `after: asyncId-${asyncId}\n`);
},
destroy(asyncId) {
fs.writeSync(1, `destroy: asyncId-${asyncId}\n`);
}
});
hook.enable();
let resource = new MyResource;
resource.asyncMethod(() => { });
resource.close();
// hook.disable(); // 注意,这里不要disable,否则将不会触发destroy事件
注:
emitDestroy
不是同步调用的,
所以emitDestroy
之后,马上将hook.disable();
,destroy
事件就不触发了。
6. async scope和async id
为了对异步资源实现跟踪,
node对每一个函数(不论异步还是同步)提供了一个 async scope,
我们可以通过调用asyncHooks.executionAsyncId();
来获取当前函数的asyncId
,
通过调用asyncHooks.triggerAsyncId();
来获取当前函数调用者的asyncId
。
const asyncHooks = require('async_hooks');
console.log(`top level: ${asyncHooks.executionAsyncId()}`);
const f = () => {
console.log(`f: ${asyncHooks.executionAsyncId()}`);
};
f();
const g = () => {
console.log(`setTimeout: ${asyncHooks.executionAsyncId()}`);
}
setTimeout(g, 0);
setTimeout(g, 0);
最终输出结果:
top level: 1
f: 1
setTimeout: 6
setTimeout: 8
注:
(1)top-level的asyncId
总是1
。
(2)调用同步函数,不会改变其调用者的asyncId
,例如,函数f
内的asyncId
和其调用者(即top-level)的asyncId
相同。
(3)同一个函数,被不同时刻进行异步调用,会分配不同的asyncId
,例如,函数g
中的asyncId
。
参考
Node.js v8.2.0 Documentation: Async Hooks
Node.js v8.x 新特性 Async Hook 简介
What does fs.writeSync(1, “a string”) mean in Node.js?