阿西河

所有教程

公众号
🌙
阿西河前端的公众号

我的收藏

    最近访问  (文章)

      教程列表

      抓包专区
      测试专区

      Node.js async_hooks 钩子回调 init

      Node.js async_hooks 钩子回调 init

      钩子回调

      Key events in the lifetime of asynchronous events have been categorized into four areas: instantiation, before/after the callback is called, and when the instance is destroyed.

      init(asyncId, type, triggerAsyncId, resource)

      • asyncId < number> A unique ID for the async resource.
      • type < string> The type of the async resource.
      • triggerAsyncId < number> The unique ID of the async resource in whose execution context this async resource was created.
      • resource < Object> Reference to the resource representing the async operation, needs to be released during destroy.

      Called when a class is constructed that has the possibility to emit an asynchronous event. This does not mean the instance must call before/after before destroy is called, only that the possibility exists.

      This behavior can be observed by doing something like opening a resource then closing it before the resource can be used. The following snippet demonstrates this.

      require('net').createServer().listen(function() { this.close(); });
      // OR
      clearTimeout(setTimeout(() => {}, 10));
      

      Every new resource is assigned an ID that is unique within the scope of the current Node.js instance.

      type

      The type is a string identifying the type of resource that caused init to be called. Generally, it will correspond to the name of the resource’s constructor.

      FSEVENTWRAP, FSREQWRAP, GETADDRINFOREQWRAP, GETNAMEINFOREQWRAP, HTTPPARSER,
      JSSTREAM, PIPECONNECTWRAP, PIPEWRAP, PROCESSWRAP, QUERYWRAP, SHUTDOWNWRAP,
      SIGNALWRAP, STATWATCHER, TCPCONNECTWRAP, TCPSERVERWRAP, TCPWRAP, TIMERWRAP,
      TTYWRAP, UDPSENDWRAP, UDPWRAP, WRITEWRAP, ZLIB, SSLCONNECTION, PBKDF2REQUEST,
      RANDOMBYTESREQUEST, TLSWRAP, Timeout, Immediate, TickObject
      

      There is also the PROMISE resource type, which is used to track Promise instances and asynchronous work scheduled by them.

      Users are able to define their own type when using the public embedder API.

      It is possible to have type name collisions. Embedders are encouraged to use unique prefixes, such as the npm package name, to prevent collisions when listening to the hooks.

      triggerAsyncId# triggerAsyncId is the asyncId of the resource that caused (or “triggered”) the new resource to initialize and that caused init to call. This is different from async_hooks.executionAsyncId() that only shows when a resource was created, while triggerAsyncId shows why a resource was created.

      The following is a simple demonstration of triggerAsyncId:

      async_hooks.createHook({
        init(asyncId, type, triggerAsyncId) {
          const eid = async_hooks.executionAsyncId();
          fs.writeSync(
            1, `${type}(${asyncId}): trigger: ${triggerAsyncId} execution: ${eid}\n`);
        }
      }).enable();
      
      require('net').createServer((conn) => {}).listen(8080);
      

      Output when hitting the server with nc localhost 8080:

      TCPSERVERWRAP(5): trigger: 1 execution: 1
      TCPWRAP(7): trigger: 5 execution: 0
      

      The TCPSERVERWRAP is the server which receives the connections.

      The TCPWRAP is the new connection from the client. When a new connection is made, the TCPWrap instance is immediately constructed. This happens outside of any JavaScript stack. (An executionAsyncId() of 0 means that it is being executed from C++ with no JavaScript stack above it.) With only that information, it would be impossible to link resources together in terms of what caused them to be created, so triggerAsyncId is given the task of propagating what resource is responsible for the new resource’s existence.

      resource

      resource is an object that represents the actual async resource that has been initialized. This can contain useful information that can vary based on the value of type. For instance, for the GETADDRINFOREQWRAP resource type, resource provides the hostname used when looking up the IP address for the host in net.Server.listen(). The API for accessing this information is currently not considered public, but using the Embedder API, users can provide and document their own resource objects. For example, such a resource object could contain the SQL query being executed.

      In the case of Promises, the resource object will have promise property that refers to the Promise that is being initialized, and an isChainedPromise property, set to true if the promise has a parent promise, and false otherwise. For example, in the case of b = a.then(handler), a is considered a parent Promise of b. Here, b is considered a chained promise.

      In some cases the resource object is reused for performance reasons, it is thus not safe to use it as a key in a WeakMap or add properties to it.

      异步上下文的示例

      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.

      let indent = 0;
      async_hooks.createHook({
        init(asyncId, type, triggerAsyncId) {
          const eid = async_hooks.executionAsyncId();
          const indentStr = ' '.repeat(indent);
          fs.writeSync(
            1,
            `${indentStr}${type}(${asyncId}):` +
            ` trigger: ${triggerAsyncId} execution: ${eid}\n`);
        },
        before(asyncId) {
          const indentStr = ' '.repeat(indent);
          fs.writeFileSync('log.out',
                           `${indentStr}before:  ${asyncId}\n`, { flag: 'a' });
          indent += 2;
        },
        after(asyncId) {
          indent -= 2;
          const indentStr = ' '.repeat(indent);
          fs.writeFileSync('log.out',
                           `${indentStr}after:  ${asyncId}\n`, { flag: 'a' });
        },
        destroy(asyncId) {
          const indentStr = ' '.repeat(indent);
          fs.writeFileSync('log.out',
                           `${indentStr}destroy:  ${asyncId}\n`, { flag: 'a' });
        },
      }).enable();
      
      require('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
      

      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.

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

      Timeout(7) -> TickObject(6) -> root(1)
      

      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 hostname is a synchronous operation, but to maintain a completely asynchronous API the user’s callback is placed in a process.nextTick().

      The graph only shows when a resource was created, not why, so to track the why use triggerAsyncId.


      更多选项请参考:Node.js 异步钩子,或者通过 点击对应菜单 进行查看;


      目录
      目录