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 的详细信息。
By default, promise executions are not assigned asyncId
s 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 0
const { 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 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
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.