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 0const { 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 6const { 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 的另一个微妙之处是 beforeafter 回调仅在链式 promise 上运行。 这意味着不是由 then()/catch() 创建的 promise 不会触发 beforeafter 回调。 更多详细信息请参见 V8 PromiseHooks API 的详细信息。

By default, promise executions are not assigned asyncIds due to the relatively expensive nature of the promise introspection API provided by V8. This means that programs using promises or async/await will not get correct execution and trigger ids for promise callback contexts by default.

import { executionAsyncId, triggerAsyncId } from 'node:async_hooks';

Promise.resolve(1729).then(() => {
  console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`);
});
// produces:
// eid 1 tid 0const { executionAsyncId, triggerAsyncId } = require('node:async_hooks');

Promise.resolve(1729).then(() => {
  console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`);
});
// produces:
// eid 1 tid 0

Observe that the then() callback claims to have executed in the context of the outer scope even though there was an asynchronous hop involved. Also, the triggerAsyncId value is 0, which means that we are missing context about the resource that caused (triggered) the then() callback to be executed.

Installing async hooks via async_hooks.createHook enables promise execution tracking:

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 6const { 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

In this example, adding any actual hook function enabled the tracking of promises. There are two promises in the example above; the promise created by Promise.resolve() and the promise returned by the call to then(). In the example above, the first promise got the asyncId 6 and the latter got asyncId 7. During the execution of the then() callback, we are executing in the context of promise with asyncId 7. This promise was triggered by async resource 6.

Another subtlety with promises is that before and after callbacks are run only on chained promises. That means promises not created by then()/catch() will not have the before and after callbacks fired on them. For more details see the details of the V8 PromiseHooks API.