Node.js我爱编程

[Node.js] async_hooks模块

2017-07-20  本文已影响1884人  何幻

1. 异步资源

node 8.2中的async_hooks模块,提供了一组API用来跟踪应用中的异步资源(asynchronous resources)。

与异步资源相关的回调函数(callback),
可能会被调用多次,例如net.createServerconnection事件,
也可能会被调用一次,例如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,它可以用来跟踪应用中所有的异步资源,
当资源在被初始化,回调之前,回调之后,销毁后,将自动触发initbeforeafterdestroy

我们可以使用hook.enable();启用跟踪,还可以使用hook.disable();来关闭。
其中asyncIdtriggerAsyncId的介绍见下文。

3. 当心console.log会造成无限循环

我们通常使用的console.log,向控制台打印消息,
然而,它却是一个异步操作(asynchronous operation),
所以,async_hooks也可以用来跟踪它。
(参考:Printing in AsyncHooks callbacks

因此,如果在上述initbeforeafterdestroy事件处理函数中出现了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?

上一篇下一篇

猜你喜欢

热点阅读