- assert断言
- async_hooks异步钩子
- async_hooks/context异步上下文
- buffer缓冲区
- C++插件
- C/C++插件(使用Node-API)
- C++嵌入器
- child_process子进程
- cluster集群
- CLI命令行
- console控制台
- Corepack核心包
- crypto加密
- crypto/webcrypto网络加密
- debugger调试器
- deprecation弃用
- dgram数据报
- diagnostics_channel诊断通道
- dns域名服务器
- domain域
- Error错误
- events事件触发器
- fs文件系统
- global全局变量
- http超文本传输协议
- http2超文本传输协议2.0
- https安全超文本传输协议
- inspector检查器
- Intl国际化
- module模块
- module/cjsCommonJS模块
- module/esmECMAScript模块
- module/package包模块
- net网络
- os操作系统
- path路径
- perf_hooks性能钩子
- permission权限
- policy安全策略
- process进程
- punycode域名代码
- querystring查询字符串
- readline逐行读取
- repl交互式解释器
- report诊断报告
- stream流
- stream/web网络流
- string_decoder字符串解码器
- test测试
- timers定时器
- tls安全传输层
- trace_events跟踪事件
- tty终端
- url网址
- util实用工具
- v8引擎
- vm虚拟机
- wasi网络汇编系统接口
- worker_threads工作线程
- zlib压缩
Node.js v16.19.1 文档
- Node.js 16.19.1
- ► 目录
-
►
索引
- assert 断言
- async_hooks 异步钩子
- async_hooks/context 异步上下文
- buffer 缓冲区
- C++插件
- C/C++插件(使用Node-API)
- C++嵌入器
- child_process 子进程
- cluster 集群
- CLI 命令行
- console 控制台
- Corepack 核心包
- crypto 加密
- crypto/webcrypto 网络加密
- debugger 调试器
- deprecation 弃用
- dgram 数据报
- diagnostics_channel 诊断通道
- dns 域名服务器
- domain 域
- Error 错误
- events 事件触发器
- fs 文件系统
- global 全局变量
- http 超文本传输协议
- http2 超文本传输协议2.0
- https 安全超文本传输协议
- inspector 检查器
- Intl 国际化
- module 模块
- module/cjs CommonJS模块
- module/esm ECMAScript模块
- module/package 包模块
- net 网络
- os 操作系统
- path 路径
- perf_hooks 性能钩子
- permission 权限
- policy 安全策略
- process 进程
- punycode 域名代码
- querystring 查询字符串
- readline 逐行读取
- repl 交互式解释器
- report 诊断报告
- stream 流
- stream/web 网络流
- string_decoder 字符串解码器
- test 测试
- timers 定时器
- tls 安全传输层
- trace_events 跟踪事件
- tty 终端
- url 网址
- util 实用工具
- v8 引擎
- vm 虚拟机
- wasi 网络汇编系统接口
- worker_threads 工作线程
- zlib 压缩
- ► 其他版本
- 文档搜索
目录
async_hooks 异步钩子#
源代码: lib/async_hooks.js
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)
#
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
之前调用 before
/after
,只是存在这种可能性。
此行为可以通过打开资源然后在资源可以使用之前关闭它来观察。 以下代码片段演示了这一点。
import { createServer } from 'node:net';
createServer().listen(function() { this.close(); });
// 或者
clearTimeout(setTimeout(() => {}, 10));
require('node:net').createServer().listen(function() { this.close(); });
// 或者
clearTimeout(setTimeout(() => {}, 10));
每个新资源都分配了一个在当前 Node.js 实例范围内唯一的 ID。
type
#
type
是字符串,标识导致调用 init
的资源类型。
一般会对应资源的构造函数名。
有效值为:
FSEVENTWRAP, FSREQCALLBACK, GETADDRINFOREQWRAP, GETNAMEINFOREQWRAP, HTTPINCOMINGMESSAGE,
HTTPCLIENTREQUEST, JSSTREAM, PIPECONNECTWRAP, PIPEWRAP, PROCESSWRAP, QUERYWRAP,
SHUTDOWNWRAP, SIGNALWRAP, STATWATCHER, TCPCONNECTWRAP, TCPSERVERWRAP, TCPWRAP,
TTYWRAP, UDPSENDWRAP, UDPWRAP, WRITEWRAP, ZLIB, SSLCONNECTION, PBKDF2REQUEST,
RANDOMBYTESREQUEST, TLSWRAP, Microtask, Timeout, Immediate, TickObject
这些值可以在任何 Node.js 版本中更改。
另外 AsyncResource
的用户可能会提供其他值。
还有 PROMISE
资源类型,用于跟踪 Promise
实例以及它们调度的异步工作。
用户可以在使用公共嵌入器 API 时定义自己的 type
。
可能存在类型名称冲突。 鼓励嵌入器使用唯一的前缀,例如 npm 包名,以防止在监听钩子时发生冲突。
triggerAsyncId
#
triggerAsyncId
是导致(或“触发”)新资源初始化并导致 init
调用的资源的 asyncId
。
这与 async_hooks.executionAsyncId()
不同,async_hooks.executionAsyncId()
只显示何时创建资源,而 triggerAsyncId
显示创建资源的原因。
下面是 triggerAsyncId
的简单演示:
import { createHook, executionAsyncId } from 'node:async_hooks';
import { stdout } from 'node:process';
import net from 'node:net';
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');
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 堆栈之外。
(0
的 executionAsyncId()
表示其是从 C++ 执行的,上面没有 JavaScript 堆栈。)只有这些信息,就不可能将资源链接在一起,因为它们是什么导致它们被创建,所以 triggerAsyncId
被赋予传播什么资源对新资源的存在负责的任务。
resource
#
resource
是一个对象,表示已初始化的实际异步资源。
这可能包含有用的信息,这些信息可能会根据 type
的值而有所不同。
例如,对于 GETADDRINFOREQWRAP
资源类型,resource
提供了在 net.Server.listen()
中查找主机 IP 地址时使用的主机名。
不支持访问此信息的 API,但使用 Embedder API,用户可以提供和记录自己的资源对象。
例如,这样的资源对象可能包含正在执行的 SQL 查询。
在某些情况下,出于性能原因,资源对象会被重用,因此将其用作 WeakMap
中的键或向其添加属性是不安全的。
异步上下文的示例#
以下是一个示例,其中包含有关 before
和 after
调用之间对 init
的调用的附加信息,特别是对 listen()
的回调将是什么样子。
输出格式稍微复杂一点,使调用上下文更容易看到。
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, () => {
// 让我们在记录服务器启动之前等待 10 毫秒。
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
各自指定当前执行上下文的值;通过调用 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
<number>
当异步操作启动(如 TCP 服务器接收新连接)或完成(如将数据写入磁盘)时,会调用回调通知用户。
before
回调在所述回调执行之前被调用。
asyncId
是分配给即将执行回调的资源的唯一标识符。
before
回调将被调用 0 到 N 次。
如果异步操作被取消,或者例如,如果 TCP 服务器没有接收到连接,则 before
回调通常会被调用 0 次。
像 TCP 服务器这样的持久异步资源通常会多次调用 before
回调,而像 fs.open()
等其他操作只会调用一次。
after(asyncId)
#
asyncId
<number>
在 before
中指定的回调完成后立即调用。
如果在回调执行期间发生未捕获的异常,则 after
将在 'uncaughtException'
事件触发或 domain
的句柄运行之后运行。
destroy(asyncId)
#
asyncId
<number>
asyncId
对应的资源销毁后调用。
它也从嵌入器 API emitDestroy()
异步调用。
有些资源依赖垃圾回收来清理,所以如果引用传给 init
的 resource
对象,可能永远不会调用 destroy
,从而导致应用程序内存泄漏。
如果资源不依赖垃圾回收,则这不是问题。
promiseResolve(asyncId)
#
asyncId
<number>
当调用传给 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 # 对应于 resolve(true)
init for PROMISE with id 6, trigger id: 5 # then() 返回的 Promise
before 6 # 输入 then() 回调
promise resolve 6 # then() 回调通过返回来解决 promise
after 6
async_hooks.executionAsyncResource()
#
- 返回: <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 '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()
#
- 返回: <number> 当前执行上下文的
asyncId
。 当有调用时对跟踪很有用。
import { executionAsyncId } from 'node:async_hooks';
console.log(executionAsyncId()); // 1 - 引导
fs.open(path, 'r', (err, fd) => {
console.log(executionAsyncId()); // 6 - open()
});
const async_hooks = require('node:async_hooks');
console.log(async_hooks.executionAsyncId()); // 1 - 引导
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();
});
默认情况下,promise 上下文可能无法获得精确的 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
#
- 返回:提供者类型到相应数字 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 嵌入的接口#
处理自己的异步资源执行 I/O、连接池或管理回调队列等任务的库开发者可以使用 AsyncResource
JavaScript API 以便调用所有适当的回调。
AsyncResource
类#
该类的文档已移至 AsyncResource
。
AsyncLocalStorage
类#
该类的文档已移至 AsyncLocalStorage
。