异步上下文示例


【Asynchronous context example】

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

【The context tracking use case is covered by the stable API AsyncLocalStorage. This example only illustrates async hooks operation but AsyncLocalStorage fits better to this use case.】

以下是一个示例,提供了有关在 beforeafter 调用之间对 init 的调用的附加信息,特别是 listen() 的回调将是什么样子。输出格式稍微复杂一些,以便更容易看到调用上下文。

【The following is an example with additional information about the calls to init between the before and after calls, specifically what the callback to listen() will look like. The output formatting is slightly more elaborate to make calling context easier to see.】

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

仅启动服务器的输出:

【Output from only starting the server:】

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 的调用来划定。

【As illustrated in the example, executionAsyncId() and execution each specify the value of the current execution context; which is delineated by calls to before and after.】

仅使用 execution 来绘制资源分配会产生以下结果:

【Only using execution to graph resource allocation results in the following:】

  root(1)
     ^
     |
TickObject(6)
     ^
     |
 Timeout(7) 

TCPSERVERWRAP 并不是这个图的一部分,尽管它是 console.log() 被调用的原因。这是因为绑定到一个没有主机名的端口是一个同步操作,但为了保持完全异步的 API,用户的回调被放在 process.nextTick() 中。这就是为什么输出中会出现 TickObject,并且它是 .listen() 回调的“父级”。

【The TCPSERVERWRAP is not part of this graph, even though it was the reason for console.log() being called. This is because binding to a port without a host name is a synchronous operation, but to maintain a completely asynchronous API the user's callback is placed in a process.nextTick(). Which is why TickObject is present in the output and is a 'parent' for .listen() callback.】

该图仅显示资源的创建时间,而不显示原因,因此要追踪原因,请使用 triggerAsyncId。其可以用以下图表示:

【The graph only shows when a resource was created, not why, so to track the why use triggerAsyncId. Which can be represented with the following graph:】

 bootstrap(1)
     |
     ˅
TCPSERVERWRAP(5)
     |
     ˅
 TickObject(6)
     |
     ˅
  Timeout(7)