序幕
async_hooks
模块提供了一个全新的功能世界,但作为 Node.js 爱好者,我最感兴趣的是,它可以让您轻松了解我们在应用程序中经常执行的一些任务的幕后情况。
在本文中,我将尝试借助async_hooks
模块来演示和解释一个典型的异步资源的生命周期。
Async Hooks API 简介
async_hooks
模块提供了一个 API 来注册回调,跟踪在 Node.js 应用程序中创建的异步资源的生命周期
异步资源表示具有关联回调的对象。此回调可能会被调用多次,例如,事件'connection'
in net.createServer()
,或者只是一次,如 in fs.open()
。也可以在调用回调之前关闭资源。AsyncHook
没有明确区分这些不同的情况,而是将它们表示为作为资源的抽象概念。
async_hooks
允许您跟踪节点应用程序中发生的任何(好吧,几乎所有)异步内容。与代码中任何回调和本机承诺的注册和调用相关的事件可以通过异步挂钩进行侦听。换句话说,此 API 允许您将侦听器附加到宏任务和微任务生命周期事件。
核心 Async Hooks API 可以用以下代码片段表示:
const async_hooks = require('async_hooks')
// 当前执行上下文的 ID
const eid = async_hooks.executionAsyncId()
// 负责触发回调的句柄的 ID
// 当前执行范围调用
const tid = async_hooks.triggerAsyncId()
const asyncHook = async_hooks.createHook({
// 在对象构造期间调用
init: function (asyncId, type, triggerAsyncId, resource) { },
// 在调用资源回调之前调用
before: function (asyncId) { },
// 调用就在资源的回调完成之后
:function (asyncId) { },
// 当 AsyncWrap 实例被销毁时调用
destroy: function (asyncId) { },
// 只为 promise 资源调用,当 `resolve`
// 函数通过时`Promise` 构造函数被调用
promiseResolve: function (asyncId) { }
})
// 开始监听异步事件
asyncHook.enable()
// 停止监听新的异步事件
asyncHook.disable()
Async Hooks API 中没有那么多函数,总的来说,它看起来很简单。
executionAsyncId()
函数返回当前执行上下文的标识符。
triggerAsyncId()
函数返回负责调用当前正在执行的回调的资源的 ID
异步资源的生命周期
异步资源是作为异步操作的一部分创建的。异步资源只不过是用于跟踪异步操作的对象。因此,它们很自然地与一个回调相关联,回调将在操作完成后执行。一旦异步资源达到其目的,它就可以像任何其他对象一样被垃圾回收。
一个非常简单的例子是setTimeout
。setTimeout
函数返回为跟踪计时器而创建的异步资源 ( Timeout
) 作为函数的返回值。setTimeout
您可以通过调用Node REPL来查看它,如下所示:
该对象包含有用的信息,例如:
- 其超时值 (
_idleTimeout
) - 定时器回调 (
_onTimeout
) - 无论是定时器还是间隔(
_repeat
) - 超时是否有效 (
_destroyed
)
这种异步资源的典型生命周期类似于
“异步钩子”方便你的是通过提供“钩子”来监视上述生命周期的不同阶段的能力,我们可以附加回调函数。有四种常见类型的钩子,如init
、before
和。它们在异步资源生命周期的以下四个阶段被触发。after
destroy
根据异步资源是否持久化(我稍后会谈到),异步资源的回调可能会执行 0 到多次。因此可以预料,对于某些异步资源, before
and after
hooks可能会被多次调用,也可能根本不会被调用。例如 with setTimeout
,before
并且每个最多after
只会触发一次,然后是 an ,而 with ,可以有多个and调用,然后是一个。init
setInterval
before
after
init
例如,同时setTimeout
创建setInterval
异步Timeout
资源。但是Timeout
创建的资源setInterval
是持久化Timeout
资源,可以通过非空__repeat
属性来标识。
Promise
objects 略有不同,它们有一个额外的 hook promiseResolve
,在 promise 被 resolved 或 rejected 后立即触发。
实际应用中的异步资源
在现实生活中的应用程序中,一个异步资源可能会在其生命周期内触发大量异步资源。考虑到上述讨论的细节,让我们看一下以下 HTTP 请求处理程序的假设示例。
这个 HTTP 请求处理程序的目的是
- 通过 HTTP 接收一些数据
- 将其存储在数据库中
- 通过 HTTP 通知上游服务
- 写一条日志消息到
stdout
假设以上四个操作只创建了四个异步资源,让我们尝试将这四个资源的时间线绘制成如下图表(时间线的长度未按比例绘制)。请花几分钟时间阅读图表,并尝试将资源的时间线与我们讨论的异步资源生命周期相关联。
设置定时器
首先,让我们看一个最简单的例子。在此示例中,我创建了一个超时为 1000 毫秒的计时器,并使用函数将“计时器回调”文本写入日志文件logger.write
当我运行上面的代码以及我设置的异步挂钩时,我将在我的log
文件中看到以下输出。
根据这个输出,
- 异步资源在被调用
Timeout
时被初始化。setTimeout
- 超时到期后
1000ms
,before
在执行计时器回调之前调用异步挂钩。 - 执行计时器回调并
"timer callback"
记录文本。 - 定时器回调执行后,
after
异步挂钩被调用。 Timeout
资源被销毁并destroy
调用了挂钩。