- 断言测试
- 异步上下文跟踪
- 异步钩子
- 缓冲(Buffer)
- C++ 插件
- 使用 Node-API 的 C/C++ 插件
- C++ 嵌入 Node环境
- 子进程(Child processes)
- 集群(Cluster)
- 命令行选项
- 控制台(Console)
- 核心包(Corepack)
- 加密(Crypto)
- 调试器(Debugger)
- 已弃用的 API
- 诊断通道(Diagnostics Channel)
- 域名系统(DNS)
- 域(Domain)
- 错误(Errors)
- 事件(Events)
- 文件系统(File system)
- 全局变量(Globals)
- HTTP
- HTTP/2
- HTTPS
- 检查器(Inspector)
- 国际化
- 模块:CommonJS 模块
- 模块:ECMAScript 模块
- 模块:
node:module
API - 模块:packages 模块
- 网络(Net)
- 系统(OS)
- 路径(Path)
- 性能挂钩(Performance hooks)
- 性能挂钩(Permissions)
- 进程(Process)
- Punycode 国际化域名编码
- 查询字符串(Query strings)
- 命令行库(Readline)
- REPL 交互式编程环境
- 诊断报告
- 单个可执行应用程序
- Stream 流
- 字符串解码器
- 单元测试
- 定时器(Timers)
- 传输层安全/SSL
- 跟踪事件
- TTY
- UDP/数据报
- URL
- 实用程序
- V8
- 虚拟机
- WebAssembly
- Web加密 API(Web Crypto API)
- 网络流 API(Web Streams API)
- 工作线程(Worker threads)
- zlib
Node.js v18.18.2 文档
- Node.js v18.18.2
- ► 目录
-
►
索引
- Assertion testing
- Asynchronous context tracking
- Async hooks
- Buffer
- C++ addons
- C/C++ addons with Node-API
- C++ embedder API
- Child processes
- Cluster
- Command-line options
- Console
- Corepack
- Crypto
- Debugger
- Deprecated APIs
- Diagnostics Channel
- DNS
- Domain
- Errors
- Events
- File system
- Globals
- HTTP
- HTTP/2
- HTTPS
- Inspector
- Internationalization
- Modules: CommonJS modules
- Modules: ECMAScript modules
- Modules:
node:module
API - Modules: Packages
- Net
- 系统(OS)
- 路径(Path)
- Performance hooks
- Permissions
- 进程(Process)
- Punycode
- Query strings
- 命令行库(Readline)
- REPL 交互式编程环境
- Report
- Single executable applications
- Stream
- String decoder
- Test runner
- Timers
- TLS/SSL
- Trace events
- TTY
- UDP/datagram
- URL
- Utilities
- V8
- VM
- WASI
- Web Crypto API
- Web Streams API
- Worker threads
- Zlib
- ► 其他版本
- ► 选项
目录
异步钩子#
createHook
、AsyncHook
和
executionAsyncResource
API,因为它们存在可用性问题、安全风险和性能影响。稳定的AsyncLocalStorage
API 可以更好地服务异步上下文跟踪用例。如果您的
createHook
、AsyncHook
或executionAsyncResource
用例超出了AsyncLocalStorage
解决的上下文跟踪需求或当前提供的诊断数据通过Diagnostics Channel ,请在https://github.com/nodejs/node/issues上打开一个问题
来描述您的用例,以便我们可以创建一个更有针对性的 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';
// Return the ID of the current execution context.
const eid = async_hooks.executionAsyncId();
// Return the ID of the handle responsible for triggering the callback of the
// current execution scope to call.
const tid = async_hooks.triggerAsyncId();
// Create a new AsyncHook instance. All of these callbacks are optional.
const asyncHook =
async_hooks.createHook({ init, before, after, destroy, promiseResolve });
// Allow callbacks of this AsyncHook instance to call. This is not an implicit
// action after running the constructor, and must be explicitly run to begin
// executing callbacks.
asyncHook.enable();
// Disable listening for new asynchronous events.
asyncHook.disable();
//
// The following are the callbacks that can be passed to createHook().
//
// init() is called during object construction. The resource may not have
// completed construction when this callback runs. Therefore, all fields of the
// resource referenced by "asyncId" may not have been populated.
function init(asyncId, type, triggerAsyncId, resource) { }
// before() is called just before the resource's callback is called. It can be
// called 0-N times for handles (such as TCPWrap), and will be called exactly 1
// time for requests (such as FSReqCallback).
function before(asyncId) { }
// after() is called just after the resource's callback has finished.
function after(asyncId) { }
// destroy() is called when the resource is destroyed.
function destroy(asyncId) { }
// promiseResolve() is called only for promise resources, when the
// resolve() function passed to the Promise constructor is invoked
// (either directly or through other means of resolving a promise).
function promiseResolve(asyncId) { }
const async_hooks = require('node:async_hooks');
// Return the ID of the current execution context.
const eid = async_hooks.executionAsyncId();
// Return the ID of the handle responsible for triggering the callback of the
// current execution scope to call.
const tid = async_hooks.triggerAsyncId();
// Create a new AsyncHook instance. All of these callbacks are optional.
const asyncHook =
async_hooks.createHook({ init, before, after, destroy, promiseResolve });
// Allow callbacks of this AsyncHook instance to call. This is not an implicit
// action after running the constructor, and must be explicitly run to begin
// executing callbacks.
asyncHook.enable();
// Disable listening for new asynchronous events.
asyncHook.disable();
//
// The following are the callbacks that can be passed to createHook().
//
// init() is called during object construction. The resource may not have
// completed construction when this callback runs. Therefore, all fields of the
// resource referenced by "asyncId" may not have been populated.
function init(asyncId, type, triggerAsyncId, resource) { }
// before() is called just before the resource's callback is called. It can be
// called 0-N times for handles (such as TCPWrap), and will be called exactly 1
// time for requests (such as FSReqCallback).
function before(asyncId) { }
// after() is called just after the resource's callback has finished.
function after(asyncId) { }
// destroy() is called when the resource is destroyed.
function destroy(asyncId) { }
// promiseResolve() is called only for promise resources, when the
// resolve() function passed to the Promise constructor is invoked
// (either directly or through other means of resolving a promise).
function promiseResolve(asyncId) { }
async_hooks.createHook(callbacks)
#
callbacks
<Object>要注册的 Hook回调init
<Function>init
回调。before
<Function>before
回调。after
<Function>after
回调。destroy
<Function>destroy
回调。promiseResolve
<Function>promiseResolve
回调。
- 返回:<AsyncHook>用于禁用和启用挂钩的实例
注册为每个异步操作的不同生命周期事件调用的函数。
在资源生命周期内,针对相应的异步事件调用回调init()
/ before()
/ after()
/ destroy()
。
所有回调都是可选的。例如,如果只需要跟踪资源清理,则只需要传递destroy
回调。可以传递给callbacks
的所有函数的详细信息位于
Hook 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) {
// Use a function like this one when debugging inside an AsyncHook callback
writeFileSync('log.out', `${format(...args)}\n`, { flag: 'a' });
}
const fs = require('node:fs');
const util = require('node:util');
function debug(...args) {
// Use a function like this one when debugging inside an AsyncHook callback
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 堆栈之外。(0
的executionAsyncId()
意味着它是从 C++ 执行的,上面没有 JavaScript 堆栈。)仅凭这些信息,就不可能将资源链接在一起导致它们被创建,因此triggerAsyncId
的任务是传播负责新资源存在的资源。
resource
#
resource
是一个对象,表示已初始化的实际异步资源。访问对象的 API 可以由资源的创建者指定。Node.js 本身创建的资源是内部的,并且可能随时更改。因此没有为这些指定 API。
在某些情况下,出于性能原因会重用资源对象,因此将其用作WeakMap
中的键或向其添加属性是不安全的。
异步上下文示例#
稳定的 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
各自指定当前执行上下文的值;这是通过调用
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)
#
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()
#
- 返回:<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'); // Private symbol to avoid pollution
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'); // Private symbol to avoid pollution
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';
import fs from 'node:fs';
console.log(executionAsyncId()); // 1 - bootstrap
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
fs.open(path, 'r', (err, fd) => {
console.log(async_hooks.executionAsyncId()); // 6 - open()
});
从executionAsyncId()
返回的 ID与执行时间有关,而不是因果关系(由triggerAsyncId()
涵盖):
const server = net.createServer((conn) => {
// Returns the ID of the server, not of the new connection, because the
// callback runs in the execution scope of the server's MakeCallback().
async_hooks.executionAsyncId();
}).listen(port, () => {
// Returns the ID of a TickObject (process.nextTick()) because all
// callbacks passed to .listen() are wrapped in a nextTick().
async_hooks.executionAsyncId();
});
默认情况下,Promise 上下文可能无法获得精确的executionAsyncIds
。请参阅有关Promise 执行跟踪的部分。
async_hooks.triggerAsyncId()
#
- 返回:<number>负责调用当前正在执行的回调的资源的 ID。
const server = net.createServer((conn) => {
// The resource that caused (or triggered) this callback to be called
// was that of the new connection. Thus the return value of triggerAsyncId()
// is the asyncId of "conn".
async_hooks.triggerAsyncId();
}).listen(port, () => {
// Even though all callbacks passed to .listen() are wrapped in a nextTick()
// the callback itself exists because the call to the server's .listen()
// was made. So the return value would be the ID of the server.
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()}`);
});
// produces:
// eid 1 tid 0
const { executionAsyncId, triggerAsyncId } = require('node:async_hooks');
Promise.resolve(1729).then(() => {
console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`);
});
// produces:
// eid 1 tid 0
请注意,then()
回调声称已在外部作用域的上下文中执行,即使涉及异步跃点也是如此。此外,triggerAsyncId
值为0
,这意味着我们缺少导致(触发)执行then()
回调的资源的上下文。
通过async_hooks.createHook
安装异步钩子可以启用 Promise 执行跟踪:
import { createHook, executionAsyncId, triggerAsyncId } from 'node:async_hooks';
createHook({ init() {} }).enable(); // forces PromiseHooks to be enabled.
Promise.resolve(1729).then(() => {
console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`);
});
// produces:
// eid 7 tid 6
const { createHook, executionAsyncId, triggerAsyncId } = require('node:async_hooks');
createHook({ init() {} }).enable(); // forces PromiseHooks to be enabled.
Promise.resolve(1729).then(() => {
console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`);
});
// produces:
// 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
#
此类的文档已移动AsyncResource
。
类:AsyncLocalStorage
#
此类的文档已移动AsyncLocalStorage
。