异步钩子
[稳定版: 1 - 实验性]
稳定版: 1 稳定性: 1 - 实验性。如果您能够迁移,请迁移离开此 API。我们不建议使用 createHook
、AsyncHook
和 executionAsyncResource
API,因为它们存在可用性问题、安全风险和性能影响。异步上下文跟踪用例最好由稳定的 AsyncLocalStorage
API 来处理。如果您对 createHook
、AsyncHook
或 executionAsyncResource
的用例超出了 AsyncLocalStorage
或 诊断通道 目前提供的诊断数据所能解决的上下文跟踪需求,请在 https://github.com/nodejs/node/issues 上创建一个 issue 来描述您的用例,以便我们能够创建一个更专注于目的的 API。
源代码: lib/async_hooks.js
我们强烈建议不要使用 async_hooks
API。其他可以涵盖其大部分用例的 API 包括:
AsyncLocalStorage
跟踪异步上下文process.getActiveResourcesInfo()
跟踪活动资源
node:async_hooks
模块提供了一个跟踪异步资源的 API。可以使用以下方式访问它:
import async_hooks from 'node:async_hooks'
const async_hooks = require('node:async_hooks')
术语
异步资源表示一个具有关联回调的对象。此回调可能被多次调用,例如 net.createServer()
中的 'connection'
事件,或者只调用一次,例如 fs.open()
。在回调被调用之前,资源也可以被关闭。AsyncHook
没有明确区分这些不同的情况,而是将它们表示为抽象概念——资源。
如果使用了 Worker
,则每个线程都有一个独立的 async_hooks
接口,并且每个线程都将使用一组新的异步 ID。
概述
以下是公共 API 的简单概述。
import async_hooks from 'node:async_hooks'
// 返回当前执行上下文的 ID。
const eid = async_hooks.executionAsyncId()
// 返回负责触发当前执行范围回调的句柄的 ID。
const tid = async_hooks.triggerAsyncId()
// 创建一个新的 AsyncHook 实例。所有这些回调都是可选的。
const asyncHook = async_hooks.createHook({ init, before, after, destroy, promiseResolve })
// 允许此 AsyncHook 实例的回调调用。这并不是运行构造函数后的隐式操作,必须显式运行才能开始执行回调。
asyncHook.enable()
// 禁用侦听新的异步事件。
asyncHook.disable()
//
// 以下是可传递给 createHook() 的回调。
//
// init() 在对象构造期间调用。当此回调运行时,资源可能尚未完成构造。因此,由“asyncId”引用的资源的所有字段可能尚未填充。
function init(asyncId, type, triggerAsyncId, resource) {}
// before() 在资源的回调被调用之前立即调用。对于句柄(例如 TCPWrap),它可以被调用 0-N 次,并且对于请求(例如 FSReqCallback),将被精确调用 1 次。
function before(asyncId) {}
// after() 在资源的回调完成后立即调用。
function after(asyncId) {}
// destroy() 在资源被销毁时调用。
function destroy(asyncId) {}
// promiseResolve() 仅对 Promise 资源调用,当调用传递给 Promise 构造函数的 resolve() 函数时(直接或通过其他解析 Promise 的方式)。
function promiseResolve(asyncId) {}
const async_hooks = require('node:async_hooks')
// 返回当前执行上下文的 ID。
const eid = async_hooks.executionAsyncId()
// 返回负责触发当前执行范围回调的句柄的 ID。
const tid = async_hooks.triggerAsyncId()
// 创建一个新的 AsyncHook 实例。所有这些回调都是可选的。
const asyncHook = async_hooks.createHook({ init, before, after, destroy, promiseResolve })
// 允许此 AsyncHook 实例的回调调用。这并不是运行构造函数后的隐式操作,必须显式运行才能开始执行回调。
asyncHook.enable()
// 禁用侦听新的异步事件。
asyncHook.disable()
//
// 以下是可传递给 createHook() 的回调。
//
// init() 在对象构造期间调用。当此回调运行时,资源可能尚未完成构造。因此,由“asyncId”引用的资源的所有字段可能尚未填充。
function init(asyncId, type, triggerAsyncId, resource) {}
// before() 在资源的回调被调用之前立即调用。对于句柄(例如 TCPWrap),它可以被调用 0-N 次,并且对于请求(例如 FSReqCallback),将被精确调用 1 次。
function before(asyncId) {}
// after() 在资源的回调完成后立即调用。
function after(asyncId) {}
// destroy() 在资源被销毁时调用。
function destroy(asyncId) {}
// promiseResolve() 仅对 Promise 资源调用,当调用传递给 Promise 构造函数的 resolve() 函数时(直接或通过其他解析 Promise 的方式)。
function promiseResolve(asyncId) {}
async_hooks.createHook(callbacks)
新增于:v8.1.0
callbacks
<Object> 要注册的 钩子回调函数init
<Function>init
回调函数。before
<Function>before
回调函数。after
<Function>after
回调函数。destroy
<Function>destroy
回调函数。promiseResolve
<Function>promiseResolve
回调函数。
返回值: <AsyncHook> 用于禁用和启用钩子的实例
注册要为每个异步操作的不同生命周期事件调用的函数。
回调函数 init()
/before()
/after()
/destroy()
在资源的生命周期中针对相应的异步事件被调用。
所有回调函数都是可选的。例如,如果只需要跟踪资源清理,则只需要传递 destroy
回调函数。可以传递给 callbacks
的所有函数的具体细节在 钩子回调函数 部分。
import { createHook } from 'node:async_hooks'
const asyncHook = createHook({
init(asyncId, type, triggerAsyncId, resource) {},
destroy(asyncId) {},
})
const async_hooks = require('node:async_hooks')
const asyncHook = async_hooks.createHook({
init(asyncId, type, triggerAsyncId, resource) {},
destroy(asyncId) {},
})
回调函数将通过原型链继承:
class MyAsyncCallbacks {
init(asyncId, type, triggerAsyncId, resource) {}
destroy(asyncId) {}
}
class MyAddedCallbacks extends MyAsyncCallbacks {
before(asyncId) {}
after(asyncId) {}
}
const asyncHook = async_hooks.createHook(new MyAddedCallbacks())
因为 Promise 是通过异步钩子机制跟踪其生命周期的异步资源,所以 init()
、before()
、after()
和 destroy()
回调函数 不能 是返回 Promise 的异步函数。
错误处理
如果任何 AsyncHook
回调抛出异常,应用程序将打印堆栈跟踪并退出。退出路径遵循未捕获异常的路径,但所有 'uncaughtException'
监听器都将被移除,从而强制进程退出。除非应用程序使用 --abort-on-uncaught-exception
运行,否则 'exit'
回调仍将被调用,在这种情况下,将打印堆栈跟踪,应用程序退出,并留下核心文件。
这种错误处理行为的原因是这些回调可能在对象的生命周期中不稳定的点运行,例如在类构造和析构期间。因此,为了防止将来意外中止,必须快速终止进程。如果进行全面的分析以确保异常可以遵循正常的控制流而不会产生意外的副作用,则将来可能会更改此设置。
在 AsyncHook
回调中打印
由于向控制台打印是异步操作,console.log()
将导致调用 AsyncHook
回调。在 AsyncHook
回调函数内使用 console.log()
或类似的异步操作将导致无限递归。调试时一个简单的解决方案是使用同步日志记录操作,例如 fs.writeFileSync(file, msg, flag)
。这将打印到文件中,并且不会递归调用 AsyncHook
,因为它同步进行。
import { writeFileSync } from 'node:fs'
import { format } from 'node:util'
function debug(...args) {
// 在 AsyncHook 回调内调试时使用此类函数
writeFileSync('log.out', `${format(...args)}\n`, { flag: 'a' })
}
const fs = require('node:fs')
const util = require('node:util')
function debug(...args) {
// 在 AsyncHook 回调内调试时使用此类函数
fs.writeFileSync('log.out', `${util.format(...args)}\n`, { flag: 'a' })
}
如果需要异步操作进行日志记录,可以使用 AsyncHook
本身提供的信息跟踪导致异步操作的原因。然后,当日志记录本身导致调用 AsyncHook
回调时,应跳过日志记录。通过这样做,可以避免无限递归。
类: AsyncHook
AsyncHook
类提供了一个接口,用于跟踪异步操作的生命周期事件。
asyncHook.enable()
- 返回值: <AsyncHook>
asyncHook
的引用。
为给定的 AsyncHook
实例启用回调。如果没有提供回调,则启用将不起作用。
AsyncHook
实例默认情况下是禁用的。如果 AsyncHook
实例应该在创建后立即启用,可以使用以下模式。
import { createHook } from 'node:async_hooks'
const hook = createHook(callbacks).enable()
const async_hooks = require('node:async_hooks')
const hook = async_hooks.createHook(callbacks).enable()
asyncHook.disable()
- 返回值: <AsyncHook>
asyncHook
的引用。
从全局 AsyncHook
回调池中禁用给定 AsyncHook
实例的回调。一旦挂钩被禁用,它将不会再次被调用,直到被启用。
为了 API 一致性,disable()
也返回 AsyncHook
实例。
钩子回调
异步事件生命周期中的关键事件已分为四个方面:实例化、回调调用之前/之后以及实例销毁时。
init(asyncId, type, triggerAsyncId, resource)
asyncId
<number> 异步资源的唯一 ID。type
<string> 异步资源的类型。triggerAsyncId
<number> 在其执行上下文中创建此异步资源的异步资源的唯一 ID。resource
<Object> 代表异步操作的资源的引用,需要在 destroy 期间释放。
在构造具有发出异步事件 可能性 的类时调用。这 并不 意味着实例必须在调用 destroy
之前调用 before
/after
,而只是表示存在这种可能性。
可以通过执行诸如打开资源然后在可以使用资源之前关闭资源之类的操作来观察此行为。以下代码片段演示了这一点。
import { createServer } from 'node:net'
createServer().listen(function () {
this.close()
})
// OR
clearTimeout(setTimeout(() => {}, 10))
require('node:net')
.createServer()
.listen(function () {
this.close()
})
// OR
clearTimeout(setTimeout(() => {}, 10))
每个新资源都会分配一个在其当前 Node.js 实例范围内唯一的 ID。
type
type
是一个字符串,用于标识导致调用 init
的资源类型。通常,它将对应于资源构造函数的名称。
Node.js 本身创建的资源的 type
可能会在任何 Node.js 版本中发生变化。有效值包括 TLSWRAP
、TCPWRAP
、TCPSERVERWRAP
、GETADDRINFOREQWRAP
、FSREQCALLBACK
、Microtask
和 Timeout
。检查所用 Node.js 版本的源代码以获取完整列表。
此外,AsyncResource
的用户可以独立于 Node.js 本身创建异步资源。
还有 PROMISE
资源类型,用于跟踪 Promise
实例和由它们调度的异步工作。
用户可以使用公共嵌入器 API 定义他们自己的 type
。
可能存在类型名称冲突。鼓励嵌入器使用唯一的 前缀(例如 npm 包名称)来防止在监听钩子时发生冲突。
triggerAsyncId
triggerAsyncId
是导致(或“触发”)新的资源初始化并导致调用 init
的资源的 asyncId
。这与仅显示资源 何时 创建的 async_hooks.executionAsyncId()
不同,而 triggerAsyncId
显示资源 为什么 创建。
以下是对 triggerAsyncId
的简单演示:
import { createHook, executionAsyncId } from 'node:async_hooks'
import { stdout } from 'node:process'
import net from 'node:net'
import fs from 'node:fs'
createHook({
init(asyncId, type, triggerAsyncId) {
const eid = executionAsyncId()
fs.writeSync(stdout.fd, `${type}(${asyncId}): trigger: ${triggerAsyncId} execution: ${eid}\n`)
},
}).enable()
net.createServer(conn => {}).listen(8080)
const { createHook, executionAsyncId } = require('node:async_hooks')
const { stdout } = require('node:process')
const net = require('node:net')
const fs = require('node:fs')
createHook({
init(asyncId, type, triggerAsyncId) {
const eid = executionAsyncId()
fs.writeSync(stdout.fd, `${type}(${asyncId}): trigger: ${triggerAsyncId} execution: ${eid}\n`)
},
}).enable()
net.createServer(conn => {}).listen(8080)
使用 nc localhost 8080
访问服务器时的输出:
TCPSERVERWRAP(5): trigger: 1 execution: 1
TCPWRAP(7): trigger: 5 execution: 0
TCPSERVERWRAP
是接收连接的服务器。
TCPWRAP
是来自客户端的新连接。建立新连接时,会立即构造 TCPWrap
实例。这发生在任何 JavaScript 堆栈之外。(executionAsyncId()
为 0
意味着它正在从 C++ 执行,上面没有 JavaScript 堆栈。)仅凭这些信息,就不可能根据什么原因创建它们来将资源关联在一起,因此 triggerAsyncId
负责传播哪个资源负责新资源的存在。
resource
resource
是一个对象,它表示已初始化的实际异步资源。访问对象的 API 可以由资源的创建者指定。Node.js 本身创建的资源是内部的,并且随时可能发生变化。因此,没有为这些资源指定 API。
在某些情况下,出于性能原因会重复使用资源对象,因此将其用作 WeakMap
中的键或向其添加属性是不安全的。
异步上下文示例
](/zh/api](/zh/api 上下文跟踪用例由稳定 API AsyncLocalStorage
涵盖。此示例仅说明异步钩子的操作,但AsyncLocalStorage
更适合此用例。
以下是一个关于 before
和 after
调用之间对 init
的调用的附加信息的示例,特别是 listen()
的回调将是什么样的。输出格式稍微复杂一些,以便更容易查看调用上下文。
import async_hooks from 'node:async_hooks'
import fs from 'node:fs'
import net from 'node:net'
import { stdout } from 'node:process'
const { fd } = stdout
let indent = 0
async_hooks
.createHook({
init(asyncId, type, triggerAsyncId) {
const eid = async_hooks.executionAsyncId()
const indentStr = ' '.repeat(indent)
fs.writeSync(fd, `${indentStr}${type}(${asyncId}):` + ` trigger: ${triggerAsyncId} execution: ${eid}\n`)
},
before(asyncId) {
const indentStr = ' '.repeat(indent)
fs.writeSync(fd, `${indentStr}before: ${asyncId}\n`)
indent += 2
},
after(asyncId) {
indent -= 2
const indentStr = ' '.repeat(indent)
fs.writeSync(fd, `${indentStr}after: ${asyncId}\n`)
},
destroy(asyncId) {
const indentStr = ' '.repeat(indent)
fs.writeSync(fd, `${indentStr}destroy: ${asyncId}\n`)
},
})
.enable()
net
.createServer(() => {})
.listen(8080, () => {
// Let's wait 10ms before logging the server started.
setTimeout(() => {
console.log('>>>', async_hooks.executionAsyncId())
}, 10)
})
const async_hooks = require('node:async_hooks')
const fs = require('node:fs')
const net = require('node:net')
const { fd } = process.stdout
let indent = 0
async_hooks
.createHook({
init(asyncId, type, triggerAsyncId) {
const eid = async_hooks.executionAsyncId()
const indentStr = ' '.repeat(indent)
fs.writeSync(fd, `${indentStr}${type}(${asyncId}):` + ` trigger: ${triggerAsyncId} execution: ${eid}\n`)
},
before(asyncId) {
const indentStr = ' '.repeat(indent)
fs.writeSync(fd, `${indentStr}before: ${asyncId}\n`)
indent += 2
},
after(asyncId) {
indent -= 2
const indentStr = ' '.repeat(indent)
fs.writeSync(fd, `${indentStr}after: ${asyncId}\n`)
},
destroy(asyncId) {
const indentStr = ' '.repeat(indent)
fs.writeSync(fd, `${indentStr}destroy: ${asyncId}\n`)
},
})
.enable()
net
.createServer(() => {})
.listen(8080, () => {
// Let's wait 10ms before logging the server started.
setTimeout(() => {
console.log('>>>', async_hooks.executionAsyncId())
}, 10)
})
仅启动服务器的输出:
TCPSERVERWRAP(5): trigger: 1 execution: 1
TickObject(6): trigger: 5 execution: 1
before: 6
Timeout(7): trigger: 6 execution: 6
after: 6
destroy: 6
before: 7
>>> 7
TickObject(8): trigger: 7 execution: 7
after: 7
before: 8
after: 8
如示例所示,executionAsyncId()
和 execution
都指定了当前执行上下文的 value;它由对 before
和 after
的调用来划分。
仅使用 execution
来绘制资源分配图会得到以下结果:
root(1)
^
|
TickObject(6)
^
|
Timeout(7)
TCPSERVERWRAP
不在此图中,即使它是调用 console.log()
的原因。这是因为绑定到端口而没有主机名是一个 同步 操作,但为了保持完全异步的 API,用户的回调被放置在 process.nextTick()
中。这就是为什么 TickObject
出现在输出中并且是 .listen()
回调的“父”元素。
该图仅显示资源 何时 创建,而不是 为什么,因此要跟踪 为什么,请使用 triggerAsyncId
。可以用下面的图来表示:
bootstrap(1)
|
˅
TCPSERVERWRAP(5)
|
˅
TickObject(6)
|
˅
Timeout(7)
before(asyncId)
asyncId
<数字>
当异步操作启动(例如 TCP 服务器接收新连接)或完成(例如将数据写入磁盘)时,将调用回调函数以通知用户。before
回调函数在执行上述回调函数之前立即调用。asyncId
是分配给即将执行回调函数的资源的唯一标识符。
before
回调函数将被调用 0 到 N 次。如果异步操作被取消,或者例如 TCP 服务器没有收到任何连接,则 before
回调函数通常将被调用 0 次。持久性异步资源(如 TCP 服务器)通常会多次调用 before
回调函数,而其他操作(如 fs.open()
)只会调用一次。
after(asyncId)
asyncId
<数字>
在 before
中指定的回调函数完成后立即调用。
如果在回调函数执行期间发生未捕获的异常,则 after
将在发出 'uncaughtException'
事件或 domain
的处理程序运行之后运行。
destroy(asyncId)
asyncId
<数字>
在对应于 asyncId
的资源被销毁后调用。它也从嵌入器 API emitDestroy()
异步调用。
某些资源依赖于垃圾回收进行清理,因此,如果对传递给 init
的 resource
对象进行了引用,则 destroy
可能永远不会被调用,从而导致应用程序出现内存泄漏。如果资源不依赖于垃圾回收,则不会出现此问题。
使用 destroy 钩子会产生额外的开销,因为它可以通过垃圾收集器跟踪 Promise
实例。
promiseResolve(asyncId)
新增于:v8.6.0
asyncId
<数字>
当传递给 Promise
构造函数的 resolve
函数被调用时(直接调用或通过其他解析 Promise 的方式调用)调用。
resolve()
不执行任何可观察到的同步工作。
如果 Promise
通过假设另一个 Promise
的状态来解析,则此时 Promise
不一定已完成或已拒绝。
new Promise(resolve => resolve(true)).then(a => {})
调用以下回调:
init for PROMISE with id 5, trigger id: 1
promise resolve 5 # corresponds to resolve(true)
init for PROMISE with id 6, trigger id: 5 # the Promise returned by then()
before 6 # the then() callback is entered
promise resolve 6 # the then() callback resolves the promise by returning
after 6
async_hooks.executionAsyncResource()
新增于:v13.9.0, v12.17.0
- 返回值:
<Object>
表示当前执行的资源。用于在资源中存储数据。
executionAsyncResource()
返回的资源对象通常是具有未公开 API 的内部 Node.js 处理对象。使用该对象上的任何函数或属性都可能导致应用程序崩溃,应避免此操作。
在顶级执行上下文中使用 executionAsyncResource()
将返回一个空对象,因为没有可用的句柄或请求对象,但拥有一个表示顶级的对象可能会有所帮助。
import { open } from 'node:fs'
import { executionAsyncId, executionAsyncResource } from 'node:async_hooks'
console.log(executionAsyncId(), executionAsyncResource()) // 1 {}
open(new URL(import.meta.url), 'r', (err, fd) => {
console.log(executionAsyncId(), executionAsyncResource()) // 7 FSReqWrap
})
const { open } = require('node:fs')
const { executionAsyncId, executionAsyncResource } = require('node:async_hooks')
console.log(executionAsyncId(), executionAsyncResource()) // 1 {}
open(__filename, 'r', (err, fd) => {
console.log(executionAsyncId(), executionAsyncResource()) // 7 FSReqWrap
})
这可以用来实现延续局部存储,而无需使用跟踪 Map
来存储元数据:
import { createServer } from 'node:http'
import { executionAsyncId, executionAsyncResource, createHook } from 'node:async_hooks'
const sym = Symbol('state') // 私有符号,避免污染
createHook({
init(asyncId, type, triggerAsyncId, resource) {
const cr = executionAsyncResource()
if (cr) {
resource[sym] = cr[sym]
}
},
}).enable()
const server = createServer((req, res) => {
executionAsyncResource()[sym] = { state: req.url }
setTimeout(function () {
res.end(JSON.stringify(executionAsyncResource()[sym]))
}, 100)
}).listen(3000)
const { createServer } = require('node:http')
const { executionAsyncId, executionAsyncResource, createHook } = require('node:async_hooks')
const sym = Symbol('state') // 私有符号,避免污染
createHook({
init(asyncId, type, triggerAsyncId, resource) {
const cr = executionAsyncResource()
if (cr) {
resource[sym] = cr[sym]
}
},
}).enable()
const server = createServer((req, res) => {
executionAsyncResource()[sym] = { state: req.url }
setTimeout(function () {
res.end(JSON.stringify(executionAsyncResource()[sym]))
}, 100)
}).listen(3000)
async_hooks.executionAsyncId()
[历史]
版本 | 变更 |
---|---|
v8.2.0 | 从 currentId 重命名。 |
v8.1.0 | v8.1.0 中添加 |
- 返回值: <number> 当前执行上下文的
asyncId
。用于跟踪某些内容的调用时间。
import { executionAsyncId } from 'node:async_hooks'
import fs from 'node:fs'
console.log(executionAsyncId()) // 1 - bootstrap
const path = '.'
fs.open(path, 'r', (err, fd) => {
console.log(executionAsyncId()) // 6 - open()
})
const async_hooks = require('node:async_hooks')
const fs = require('node:fs')
console.log(async_hooks.executionAsyncId()) // 1 - bootstrap
const path = '.'
fs.open(path, 'r', (err, fd) => {
console.log(async_hooks.executionAsyncId()) // 6 - open()
})
executionAsyncId()
返回的 ID 与执行时间相关,而不是因果关系(因果关系由 triggerAsyncId()
涵盖):
const server = net
.createServer(conn => {
// 返回服务器的 ID,而不是新连接的 ID,因为回调在服务器的 MakeCallback() 的执行范围内运行。
async_hooks.executionAsyncId()
})
.listen(port, () => {
// 返回 TickObject (process.nextTick()) 的 ID,因为传递给 .listen() 的所有回调都包装在 nextTick() 中。
async_hooks.executionAsyncId()
})
async_hooks 上下文默认情况下可能无法获得精确的 executionAsyncIds
。请参阅关于 Promise 执行跟踪 的部分。
async_hooks.triggerAsyncId()
- 返回值:
<number>
当前正在执行的回调的调用方资源的 ID。
const server = net
.createServer(conn => {
// 导致(或触发)调用此回调的资源是新的连接的资源。因此,triggerAsyncId() 的返回值是“conn”的 asyncId。
async_hooks.triggerAsyncId()
})
.listen(port, () => {
// 即使传递给 .listen() 的所有回调都包装在 nextTick() 中,
// 回调本身也存在,因为对服务器的 .listen() 的调用已完成。因此,返回值将是服务器的 ID。
async_hooks.triggerAsyncId()
})
Promise 上下文可能默认情况下无法获得有效的 triggerAsyncId
。请参阅关于Promise 执行跟踪的部分。
async_hooks.asyncWrapProviders
新增于:v17.2.0, v16.14.0
- 返回值:提供程序类型与其对应的数字 ID 的映射。此映射包含
async_hooks.init()
事件可能发出的所有事件类型。
此功能禁止使用已弃用的 process.binding('async_wrap').Providers
。请参阅:DEP0111
Promise 执行跟踪
默认情况下,由于 V8 提供的 Promise 内省 API 相对昂贵,因此 Promise 执行不会分配 asyncId
。这意味着使用 Promise 或 async
/await
的程序默认情况下不会获得正确的执行和触发 Promise 回调上下文的 id。
import { executionAsyncId, triggerAsyncId } from 'node:async_hooks'
Promise.resolve(1729).then(() => {
console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`)
})
// 输出:
// eid 1 tid 0
const { executionAsyncId, triggerAsyncId } = require('node:async_hooks')
Promise.resolve(1729).then(() => {
console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`)
})
// 输出:
// eid 1 tid 0
可以看到,then()
回调声称是在外部作用域的上下文中执行的,即使涉及异步跳转。此外,triggerAsyncId
值为 0
,这意味着我们缺少关于导致(触发)then()
回调执行的资源的上下文。
通过 async_hooks.createHook
安装异步钩子可以启用 Promise 执行跟踪:
import { createHook, executionAsyncId, triggerAsyncId } from 'node:async_hooks'
createHook({ init() {} }).enable() // 强制启用 PromiseHooks。
Promise.resolve(1729).then(() => {
console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`)
})
// 输出:
// eid 7 tid 6
const { createHook, executionAsyncId, triggerAsyncId } = require('node:async_hooks')
createHook({ init() {} }).enable() // 强制启用 PromiseHooks。
Promise.resolve(1729).then(() => {
console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`)
})
// 输出:
// eid 7 tid 6
在这个例子中,添加任何实际的钩子函数都启用了 Promise 的跟踪。上面的例子中有两个 Promise;由 Promise.resolve()
创建的 Promise 和 then()
调用返回的 Promise。在上面的例子中,第一个 Promise 获取了 asyncId
6
,第二个获取了 asyncId
7
。在 then()
回调的执行过程中,我们正在 asyncId
为 7
的 Promise 的上下文中执行。这个 Promise 由异步资源 6
触发。
Promise 的另一个细微之处是 before
和 after
回调只在链式 Promise 上运行。这意味着不是由 then()
/catch()
创建的 Promise 不会触发 before
和 after
回调。更多细节请参见 V8 PromiseHooks API 的详细信息。
JavaScript 嵌入器 API
处理自身异步资源的库开发者(执行诸如 I/O、连接池或管理回调队列之类的任务)可以使用 AsyncResource
JavaScript API,以便调用所有相应的回调。
类: AsyncResource
](/zh/api 此类的文档已移至 AsyncResource
。
类: AsyncLocalStorage
](/zh/api 此类的文档已移至 AsyncLocalStorage
。