Skip to content

Async hooks

[安定版: 1 - 試験的]

安定版: 1 安定性: 1 - 試験的。可能であれば、この API から移行してください。createHookAsyncHook、およびexecutionAsyncResource API は、使い勝手の問題、安全上のリスク、およびパフォーマンスへの影響があるため、使用はお勧めしません。非同期コンテキスト追跡のユースケースは、安定したAsyncLocalStorage API の方が適しています。AsyncLocalStorageまたはDiagnostics Channelによって現在提供されている診断データで解決されるコンテキスト追跡のニーズを超えたcreateHookAsyncHook、またはexecutionAsyncResourceのユースケースがある場合は、ユースケースを説明する問題をhttps://github.com/nodejs/node/issuesで提起してください。より目的重視の API を作成できます。

ソースコード: lib/async_hooks.js

async_hooks API の使用は強く推奨しません。そのユースケースの大部分をカバーできる他の API には以下が含まれます。

node:async_hooks モジュールは、非同期リソースを追跡するための API を提供します。これは次のようにアクセスできます。

js
import async_hooks from 'node:async_hooks'
js
const async_hooks = require('node:async_hooks')

用語

非同期リソースは、関連付けられたコールバックを持つオブジェクトを表します。このコールバックは、net.createServer()'connection'イベントのように複数回呼び出される場合もあれば、fs.open()のように一度だけ呼び出される場合もあります。リソースは、コールバックが呼び出される前に閉じられることもあります。AsyncHookはこれらの異なるケースを明示的に区別しませんが、それらをリソースという抽象的な概念として表します。

Workerが使用されている場合、各スレッドは独立したasync_hooksインターフェースを持ち、各スレッドは新しい一連の非同期 ID を使用します。

概要

公開 API の簡単な概要を以下に示します。

js
import async_hooks from 'node:async_hooks'

// 現在の実行コンテキストのIDを返します。
const eid = async_hooks.executionAsyncId()

// 現在の実行スコープのコールバックの呼び出しをトリガーするハンドルIDを返します。
const tid = async_hooks.triggerAsyncId()

// 新しいAsyncHookインスタンスを作成します。これらのコールバックはすべてオプションです。
const asyncHook = async_hooks.createHook({ init, before, after, destroy, promiseResolve })

// このAsyncHookインスタンスのコールバックの呼び出しを許可します。これはコンストラクタの実行後に行われる暗黙的な動作ではなく、コールバックの実行を開始するには明示的に実行する必要があります。
asyncHook.enable()

// 新しい非同期イベントのリスニングを無効にします。
asyncHook.disable()

//
// 以下のコールバックは、createHook()に渡すことができます。
//

// init()はオブジェクトの構築中に呼び出されます。このコールバックが実行されるときに、リソースの構築が完了しているとは限りません。「asyncId」によって参照されるリソースのすべてのフィールドが設定されているとは限りません。
function init(asyncId, type, triggerAsyncId, resource) {}

// before()は、リソースのコールバックが呼び出される直前に呼び出されます。ハンドル(TCPWrapなど)に対しては0〜N回呼び出され、リクエスト(FSReqCallbackなど)に対しては正確に1回呼び出されます。
function before(asyncId) {}

// after()は、リソースのコールバックが終了した直後に呼び出されます。
function after(asyncId) {}

// destroy()は、リソースが破棄されたときに呼び出されます。
function destroy(asyncId) {}

// promiseResolve()は、Promiseコンストラクタに渡されたresolve()関数が呼び出されたとき(直接またはプロミスを解決する他の手段によって)のみ、プロミスリソースに対して呼び出されます。
function promiseResolve(asyncId) {}
js
const async_hooks = require('node:async_hooks')

// 現在の実行コンテキストのIDを返します。
const eid = async_hooks.executionAsyncId()

// 現在の実行スコープのコールバックの呼び出しをトリガーするハンドルIDを返します。
const tid = async_hooks.triggerAsyncId()

// 新しいAsyncHookインスタンスを作成します。これらのコールバックはすべてオプションです。
const asyncHook = async_hooks.createHook({ init, before, after, destroy, promiseResolve })

// このAsyncHookインスタンスのコールバックの呼び出しを許可します。これはコンストラクタの実行後に行われる暗黙的な動作ではなく、コールバックの実行を開始するには明示的に実行する必要があります。
asyncHook.enable()

// 新しい非同期イベントのリスニングを無効にします。
asyncHook.disable()

//
// 以下のコールバックは、createHook()に渡すことができます。
//

// init()はオブジェクトの構築中に呼び出されます。このコールバックが実行されるときに、リソースの構築が完了しているとは限りません。「asyncId」によって参照されるリソースのすべてのフィールドが設定されているとは限りません。
function init(asyncId, type, triggerAsyncId, resource) {}

// before()は、リソースのコールバックが呼び出される直前に呼び出されます。ハンドル(TCPWrapなど)に対しては0〜N回呼び出され、リクエスト(FSReqCallbackなど)に対しては正確に1回呼び出されます。
function before(asyncId) {}

// after()は、リソースのコールバックが終了した直後に呼び出されます。
function after(asyncId) {}

// destroy()は、リソースが破棄されたときに呼び出されます。
function destroy(asyncId) {}

// promiseResolve()は、Promiseコンストラクタに渡されたresolve()関数が呼び出されたとき(直接またはプロミスを解決する他の手段によって)のみ、プロミスリソースに対して呼び出されます。
function promiseResolve(asyncId) {}

async_hooks.createHook(callbacks)

追加日時: v8.1.0

各非同期操作の異なるライフタイムイベントに対して呼び出される関数を登録します。

コールバック init()/before()/after()/destroy() は、リソースのライフタイム中にそれぞれの非同期イベントに対して呼び出されます。

すべてのコールバックはオプションです。たとえば、リソースのクリーンアップのみを追跡する必要がある場合は、destroy コールバックのみを渡す必要があります。callbacks に渡すことができるすべての関数の詳細は、フックコールバック セクションを参照してください。

js
import { createHook } from 'node:async_hooks'

const asyncHook = createHook({
  init(asyncId, type, triggerAsyncId, resource) {},
  destroy(asyncId) {},
})
js
const async_hooks = require('node:async_hooks')

const asyncHook = async_hooks.createHook({
  init(asyncId, type, triggerAsyncId, resource) {},
  destroy(asyncId) {},
})

コールバックはプロトタイプチェーンを介して継承されます。

js
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が再帰的に呼び出されることはありません。

js
import { writeFileSync } from 'node:fs'
import { format } from 'node:util'

function debug(...args) {
  // AsyncHookコールバック内でデバッグする際に、このような関数を使用します
  writeFileSync('log.out', `${format(...args)}\n`, { flag: 'a' })
}
js
const fs = require('node:fs')
const util = require('node:util')

function debug(...args) {
  // AsyncHookコールバック内でデバッグする際に、このような関数を使用します
  fs.writeFileSync('log.out', `${util.format(...args)}\n`, { flag: 'a' })
}

ログに非同期操作が必要な場合は、AsyncHook自体によって提供される情報を使用して、非同期操作の原因を追跡することができます。その後、ログ自体がAsyncHookコールバックの呼び出しの原因であった場合は、ログをスキップする必要があります。これにより、無限再帰が回避されます。

クラス: AsyncHook

AsyncHookクラスは、非同期操作のライフタイムイベントを追跡するためのインターフェースを提供します。

asyncHook.enable()

指定されたAsyncHookインスタンスのコールバックを有効にします。コールバックが提供されていない場合、有効化はノーオペレーションです。

AsyncHookインスタンスはデフォルトで無効になっています。AsyncHookインスタンスを生成直後に有効にするには、次のパターンを使用できます。

js
import { createHook } from 'node:async_hooks'

const hook = createHook(callbacks).enable()
js
const async_hooks = require('node:async_hooks')

const hook = async_hooks.createHook(callbacks).enable()

asyncHook.disable()

実行されるAsyncHookコールバックのグローバルプールから、指定されたAsyncHookインスタンスのコールバックを無効にします。フックが無効にされると、有効になるまで再び呼び出されません。

API の一貫性のため、disable()AsyncHookインスタンスを返します。

フックコールバック

非同期イベントのライフタイムにおける主要なイベントは、インスタンス化、コールバックの呼び出し前/後、インスタンスの破棄の 4 つの領域に分類されています。

init(asyncId, type, triggerAsyncId, resource)

  • asyncId <number> 非同期リソースの一意の ID。
  • type <string> 非同期リソースの型。
  • triggerAsyncId <number> この非同期リソースが作成された実行コンテキスト内の非同期リソースの一意の ID。
  • resource <Object> 非同期操作を表すリソースへの参照。destroy中に解放する必要があります。

非同期イベントを発生させる可能性のあるクラスが構築されたときに呼び出されます。これは、インスタンスがdestroyが呼び出される前にbefore/afterを呼び出す必要があるという意味ではありません。可能性が存在するという意味だけです。

この動作は、リソースを開いてから、リソースを使用する前に閉じることによって観察できます。次のスニペットはこのことを示しています。

js
import { createServer } from 'node:net'

createServer().listen(function () {
  this.close()
})
// OR
clearTimeout(setTimeout(() => {}, 10))
js
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リソースタイプもあります。

ユーザーは、公開エンベッダー API を使用するときに独自のtypeを定義できます。

型名の衝突が発生する可能性があります。フックをリッスンする際に衝突を防ぐために、エンベッダーは npm パッケージ名などの固有のプレフィックスを使用することをお勧めします。

triggerAsyncId

triggerAsyncIdは、新しいリソースの初期化(または「トリガー」)の原因となったリソースのasyncIdであり、initの呼び出しの原因となったものです。これは、リソースが作成された時点のみを示すasync_hooks.executionAsyncId()とは異なり、triggerAsyncIdはリソースが作成された理由を示します。

triggerAsyncIdの簡単なデモを以下に示します。

js
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)
js
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でサーバーにアクセスしたときの出力:

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

TCPSERVERWRAPは接続を受け取るサーバーです。

TCPWRAPはクライアントからの新しい接続です。新しい接続が確立されると、TCPWrapインスタンスはすぐに構築されます。これは、あらゆる JavaScript スタックの外で行われます。(executionAsyncId()0であることは、上記の JavaScript スタックがない C++から実行されていることを意味します。)その情報だけでは、何が原因で作成されたかという点でリソースをリンクすることは不可能であるため、triggerAsyncIdに新しいリソースの存在に責任があるリソースを伝播するというタスクが与えられます。

resource

resourceは、初期化された実際の非同期リソースを表すオブジェクトです。オブジェクトにアクセスするための API は、リソースの作成者によって指定される場合があります。Node.js 自身によって作成されたリソースは内部的なものであり、いつでも変更される可能性があります。そのため、これらについては API は指定されていません。

パフォーマンス上の理由から、場合によってはリソースオブジェクトが再利用されます。そのため、WeakMapのキーとして使用したり、プロパティを追加したりすることは安全ではありません。

非同期コンテキストの例

コンテキスト追跡のユースケースは、安定した API であるAsyncLocalStorageでカバーされています。この例は非同期フックの動作を示すものですが、AsyncLocalStorageの方がこのユースケースには適しています。

以下は、beforeafterの呼び出し間のinitへの呼び出しに関する追加情報、特にlisten()へのコールバックがどのように見えるかについての例です。呼び出しコンテキストを見やすくするために、出力フォーマットを少し詳しくしています。

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

サーバーの起動のみからの出力:

bash
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のみを使用してリソース割り当てのグラフを作成すると、次のようになります。

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

console.log()が呼び出された理由であるにもかかわらず、TCPSERVERWRAPはこのグラフの一部ではありません。これは、ホスト名のないポートへのバインディングが同期操作であるためですが、完全に非同期 API を維持するために、ユーザーのコールバックはprocess.nextTick()に配置されます。そのため、出力にTickObjectが存在し、.listen()コールバックの「親」となっています。

このグラフはリソースがいつ作成されたかを示しているだけで、なぜ作成されたかを示していません。そのため、なぜを追跡するにはtriggerAsyncIdを使用します。これは次のグラフで表すことができます。

bash
 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() から非同期的に呼び出されます。

一部のリソースはクリーンアップにガベージコレクションに依存するため、init に渡された resource オブジェクトへの参照が作成されている場合、destroy が呼び出されない可能性があり、アプリケーションでメモリリークが発生する可能性があります。リソースがガベージコレクションに依存しない場合は、これは問題になりません。

destroy フックを使用すると、ガベージコレクターを介してPromise インスタンスの追跡が可能になるため、追加のオーバーヘッドが発生します。

promiseResolve(asyncId)

追加されたバージョン: v8.6.0

Promise コンストラクターに渡されたresolve 関数が呼び出されたときに呼び出されます(直接、または Promise を解決する他の手段を通して)。

resolve() は、観測可能な同期的な作業を一切行いません。

Promise が別のPromise の状態を仮定して解決された場合、この時点ではPromise が必ずしも履行または拒否されるとは限りません。

js
new Promise(resolve => resolve(true)).then(a => {})

は、次のコールバックを呼び出します。

text
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()

追加日: v13.9.0, v12.17.0

  • 戻り値: <Object> 現在の処理を表すリソース。リソース内にデータを格納するのに役立ちます。

executionAsyncResource()によって返されるリソースオブジェクトは、ほとんどの場合、文書化されていない API を持つ内部 Node.js ハンドルオブジェクトです。オブジェクトの関数やプロパティを使用すると、アプリケーションがクラッシュする可能性があり、避ける必要があります。

トップレベルの実行コンテキストでexecutionAsyncResource()を使用すると、使用できるハンドルまたはリクエストオブジェクトがないため空のオブジェクトが返されますが、トップレベルを表すオブジェクトがあると便利です。

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

これは、メタデータを格納するための追跡Mapを使用せずに、継続ローカルストレージを実装するために使用できます。

js
import { createServer } from 'node:http'
import { executionAsyncId, executionAsyncResource, createHook } from 'node:async_hooks'
const sym = Symbol('state') // プライベートシンボルを使用して汚染を避ける

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)
js
const { createServer } = require('node:http')
const { executionAsyncId, executionAsyncResource, createHook } = require('node:async_hooks')
const sym = Symbol('state') // プライベートシンボルを使用して汚染を避ける

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

[履歴]

バージョン変更
v8.2.0currentIdから名前変更
v8.1.0追加: v8.1.0
  • 戻り値: <number> 現在の実行コンテキストのasyncId。何かが呼び出された時点を追跡するのに役立ちます。
js
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()
})
js
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()で扱われる)ではなく、実行タイミングに関連しています。

js
const server = net
  .createServer(conn => {
    // コールバックはサーバーのMakeCallback()の実行スコープ内で実行されるため、新しい接続ではなくサーバーのIDを返します。
    async_hooks.executionAsyncId()
  })
  .listen(port, () => {
    // .listen()に渡されるすべてのコールバックはnextTick()でラップされているため、TickObject(process.nextTick())のIDを返します。
    async_hooks.executionAsyncId()
  })

デフォルトでは、Promise コンテキストは正確なexecutionAsyncIdsを取得できない場合があります。「Promise 実行の追跡」セクションを参照してください。

async_hooks.triggerAsyncId()

  • 戻り値: <number> 現在実行されているコールバックの呼び出し元であるリソースの ID。
js
const server = net
  .createServer(conn => {
    // このコールバックの呼び出しの原因(またはトリガー)となったリソースは、新しい接続のリソースです。そのため、triggerAsyncId()の戻り値は「conn」のasyncIdです。
    async_hooks.triggerAsyncId()
  })
  .listen(port, () => {
    // .listen()に渡されるすべてのコールバックはnextTick()でラップされていますが、コールバック自体はサーバーの.listen()への呼び出しが行われたために存在します。そのため、戻り値はサーバーのIDになります。
    async_hooks.triggerAsyncId()
  })

デフォルトでは、Promise コンテキストは有効なtriggerAsyncIdを取得できない場合があります。「Promise 実行の追跡」セクションを参照してください。

async_hooks.asyncWrapProviders

追加日時: v17.2.0, v16.14.0

  • 戻り値: プロバイダの種類と対応する数値 ID のマップ。このマップには、async_hooks.init() イベントによって発行される可能性のあるすべてのイベントタイプが含まれています。

この機能は、非推奨の process.binding('async_wrap').Providers の使用を抑制します。参照: DEP0111

Promise 実行の追跡

デフォルトでは、Promise イントロスペクション API(V8 提供)の比較的コストのかかる性質のために、Promise の実行にはasyncIdが割り当てられません。これは、Promise またはasync/awaitを使用するプログラムでは、デフォルトで Promise コールバックコンテキストの正確な実行とトリガー ID が取得されないことを意味します。

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

Promise.resolve(1729).then(() => {
  console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`)
})
// 出力:
// eid 1 tid 0
js
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 実行の追跡が可能になります。

js
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
js
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 の追跡が可能になりました。上記の例には 2 つの Promise があります。Promise.resolve()によって作成された Promise と、then()への呼び出しによって返された Promise です。上記の例では、最初の Promise にasyncId 6が、後者の Promise にasyncId 7が割り当てられました。then()コールバックの実行中は、asyncId 7を持つ Promise のコンテキストで実行されています。この Promise は、非同期リソース6によってトリガーされました。

Promise に関するもう一つの微妙な点は、beforeおよびafterコールバックがチェーンされた Promise でのみ実行されることです。つまり、then()/catch()によって作成されていない Promise では、beforeおよびafterコールバックは実行されません。詳細については、V8 のPromiseHooks API の詳細を参照してください。

JavaScript embedder API

I/O、接続プーリング、またはコールバックキューの管理などのタスクを実行する独自の非同期リソースを処理するライブラリ開発者は、すべての適切なコールバックが呼び出されるようにAsyncResource JavaScript API を使用できます。

クラス: AsyncResource

このクラスのドキュメントはAsyncResourceに移動しました。

クラス: AsyncLocalStorage

このクラスのドキュメントはAsyncLocalStorageに移動しました。