Async hooks#

稳定性:1 - 实验性。如果可以,请迁移出此 API。我们不建议使用 createHookAsyncHookexecutionAsyncResource API,因为它们存在可用性问题、安全风险和性能影响。异步上下文跟踪的用例最好由稳定的 AsyncLocalStorage API 来满足。如果您对 createHookAsyncHookexecutionAsyncResource 有超出 AsyncLocalStorage 所解决的上下文跟踪需求,或者超出 Diagnostics Channel 目前提供的诊断数据的使用场景,请在 https://github.com/nodejs/node/issues 提交议题描述您的用例,以便我们创建更具针对性的 API。

我们强烈建议不要使用 async_hooks API。其他可以覆盖其大部分用例的 API 包括:

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(options)#

注册在每个异步操作的不同生命周期事件中被调用的函数。

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) {
  // 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 实例,可以使用以下模式。

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 实例的回调。一旦钩子被禁用,在重新启用之前它将不会再次被调用。

为了 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 版本中都可能发生变化。有效值包括 TLSWRAPTCPWRAPTCPSERVERWRAPGETADDRINFOREQWRAPFSREQCALLBACKMicrotaskTimeout。检查所使用的 Node.js 版本的源代码以获取完整列表。

此外,AsyncResource 的用户可以创建独立于 Node.js 本身的异步资源。

还有一个 PROMISE 资源类型,用于跟踪 Promise 实例及其调度的异步工作。只有在 trackPromises 选项设置为 true 时,才会跟踪 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 堆栈之外。(0executionAsyncId() 意味着它是在没有 JavaScript 堆栈的情况下从 C++ 执行的。)仅凭该信息,无法在资源创建原因方面将它们链接起来,因此 triggerAsyncId 被赋予了传播是哪个资源导致了新资源存在的任务。

resource#

resource 是一个表示已初始化的实际异步资源的对象。访问该对象的 API 可能由资源的创建者指定。Node.js 本身创建的资源是内部的,并且随时可能发生变化。因此,没有为这些资源指定 API。

在某些情况下,出于性能原因会重用资源对象,因此将其用作 WeakMap 中的键或向其添加属性是不安全的。

异步上下文示例#

上下文跟踪用例由稳定的 API AsyncLocalStorage 覆盖。此示例仅演示异步钩子的操作,但 AsyncLocalStorage 更适合此用例。

以下是一个包含有关 beforeafter 调用之间 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 分别指定了当前执行上下文的值;该值由 beforeafter 的调用所界定。

仅使用 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)#

当异步操作启动(例如 TCP 服务器接收新连接)或完成(例如将数据写入磁盘)时,会调用一个回调来通知用户。before 回调在所述回调执行前被调用。asyncId 是分配给即将执行回调的资源的唯一标识符。

before 回调将被调用 0 到 N 次。如果异步操作被取消,或者例如 TCP 服务器没有接收到连接,before 回调通常会被调用 0 次。持久性的异步资源(如 TCP 服务器)通常会多次调用 before 回调,而其他操作(如 fs.open())只会调用一次。

after(asyncId)#

before 中指定的回调完成后立即调用。

如果在执行回调期间发生未捕获的异常,则 after 将在 'uncaughtException' 事件触发或 domain 的处理程序运行之后运行。

destroy(asyncId)#

在与 asyncId 对应的资源销毁后调用。它也从嵌入器 API emitDestroy() 异步调用。

某些资源依赖垃圾回收进行清理,因此如果对传递给 initresource 对象保持引用,则 destroy 可能永远不会被调用,从而导致应用程序内存泄漏。如果资源不依赖垃圾回收,则不会出现此问题。

使用销毁钩子会导致额外的开销,因为它通过垃圾回收器启用了对 Promise 实例的跟踪。

promiseResolve(asyncId)#

当传递给 Promise 构造函数的 resolve 函数被调用时(直接调用或通过其他方式解析 promise),将调用此回调。

resolve() 不执行任何可观察的同步工作。

如果 Promise 是通过采用另一个 Promise 的状态来解析的,则 Promise 在此时不一定已完成(fulfilled)或拒绝(rejected)。

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
});

这可用于实现延续本地存储(continuation local storage),而无需使用跟踪 Map 来存储元数据。

import { createServer } from 'node:http';
import {
  executionAsyncId,
  executionAsyncResource,
  createHook,
} from '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);
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
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) => {
  // 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 和触发 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() 回调执行期间,我们在 asyncId7 的 Promise 上下文中执行。此 Promise 由异步资源 6 触发。

Promise 的另一个微妙之处在于 beforeafter 回调仅在链式 Promise 上运行。这意味着不是由 then()/catch() 创建的 Promise 不会触发它们的 beforeafter 回调。有关更多详细信息,请参阅 V8 PromiseHooks API 的详情。

禁用 Promise 执行跟踪#

跟踪 Promise 执行会导致显著的性能开销。要选择退出 Promise 跟踪,请将 trackPromises 设置为 false

const { createHook } = require('node:async_hooks');
const { writeSync } = require('node:fs');
createHook({
  init(asyncId, type, triggerAsyncId, resource) {
    // This init hook does not get called when trackPromises is set to false.
    writeSync(1, `init hook triggered for ${type}\n`);
  },
  trackPromises: false,  // Do not track promises.
}).enable();
Promise.resolve(1729);
import { createHook } from 'node:async_hooks';
import { writeSync } from 'node:fs';

createHook({
  init(asyncId, type, triggerAsyncId, resource) {
    // This init hook does not get called when trackPromises is set to false.
    writeSync(1, `init hook triggered for ${type}\n`);
  },
  trackPromises: false,  // Do not track promises.
}).enable();
Promise.resolve(1729);

JavaScript 嵌入器 API#

处理其自身执行 I/O、连接池管理或管理回调队列等异步资源的库开发者可以使用 AsyncResource JavaScript API,以便调用所有适当的回调。

类:AsyncResource#

该类的文档已移动至 AsyncResource

类:AsyncLocalStorage#

该类的文档已移动至 AsyncLocalStorage