1. 異步資源
node 8.2中的async_hooks模塊,提供了一組API用來跟蹤應(yīng)用中的異步資源(asynchronous resources)。
與異步資源相關(guān)的回調(diào)函數(shù)(callback),
可能會被調(diào)用多次,例如net.createServer的connection事件,
也可能會被調(diào)用一次,例如fs.open。
異步資源也可能在調(diào)用回調(diào)函數(shù)之前就已經(jīng)被關(guān)閉了。
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();
以上代碼創(chuàng)建一個(gè)hook,它可以用來跟蹤應(yīng)用中所有的異步資源,
當(dāng)資源在被初始化,回調(diào)之前,回調(diào)之后,銷毀后,將自動(dòng)觸發(fā)init,before,after,destroy。
我們可以使用hook.enable();啟用跟蹤,還可以使用hook.disable();來關(guān)閉。
其中asyncId,triggerAsyncId的介紹見下文。
3. 當(dāng)心console.log會造成無限循環(huán)
我們通常使用的console.log,向控制臺打印消息,
然而,它卻是一個(gè)異步操作(asynchronous operation),
所以,async_hooks也可以用來跟蹤它。
(參考:Printing in AsyncHooks callbacks)
因此,如果在上述init,before,after,destroy事件處理函數(shù)中出現(xiàn)了console.log,就會導(dǎo)致無限循環(huán)。
我們可以使用fs.writeSync(1, msg)來代替console.log,
其中writeSync函數(shù)的第一個(gè)參數(shù)為文件描述符(file descriptor),
1表示標(biāo)準(zhǔn)輸出(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,否則只能觸發(fā)init事件
執(zhí)行后,輸出:
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中內(nèi)置的異步資源,還可以跟蹤自定義的資源,
要做到這一點(diǎn),我們需要繼承AsyncResource類,然后手動(dòng)觸發(fā)事件。
其中,AsyncResource類,是async_hooks模塊導(dǎo)出對象的一個(gè)屬性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,否則將不會觸發(fā)destroy事件
注:
emitDestroy不是同步調(diào)用的,
所以emitDestroy之后,馬上將hook.disable();,destroy事件就不觸發(fā)了。
6. async scope和async id
為了對異步資源實(shí)現(xiàn)跟蹤,
node對每一個(gè)函數(shù)(不論異步還是同步)提供了一個(gè) async scope,
我們可以通過調(diào)用asyncHooks.executionAsyncId();來獲取當(dāng)前函數(shù)的asyncId,
通過調(diào)用asyncHooks.triggerAsyncId();來獲取當(dāng)前函數(shù)調(diào)用者的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);
最終輸出結(jié)果:
top level: 1
f: 1
setTimeout: 6
setTimeout: 8
注:
(1)top-level的asyncId總是1。
(2)調(diào)用同步函數(shù),不會改變其調(diào)用者的asyncId,例如,函數(shù)f內(nèi)的asyncId和其調(diào)用者(即top-level)的asyncId相同。
(3)同一個(gè)函數(shù),被不同時(shí)刻進(jìn)行異步調(diào)用,會分配不同的asyncId,例如,函數(shù)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?