Async hooks
[安定版: 1 - 試験的]
安定版: 1 安定性: 1 - 試験的。可能であれば、この API から移行してください。createHook
、AsyncHook
、およびexecutionAsyncResource
API は、使い勝手の問題、安全上のリスク、およびパフォーマンスへの影響があるため、使用はお勧めしません。非同期コンテキスト追跡のユースケースは、安定したAsyncLocalStorage
API の方が適しています。AsyncLocalStorage
またはDiagnostics Channelによって現在提供されている診断データで解決されるコンテキスト追跡のニーズを超えたcreateHook
、AsyncHook
、またはexecutionAsyncResource
のユースケースがある場合は、ユースケースを説明する問題をhttps://github.com/nodejs/node/issuesで提起してください。より目的重視の API を作成できます。
ソースコード: lib/async_hooks.js
async_hooks
API の使用は強く推奨しません。そのユースケースの大部分をカバーできる他の API には以下が含まれます。
AsyncLocalStorage
は非同期コンテキストを追跡します。process.getActiveResourcesInfo()
はアクティブなリソースを追跡します。
node:async_hooks
モジュールは、非同期リソースを追跡するための API を提供します。これは次のようにアクセスできます。
import async_hooks from 'node:async_hooks'
const async_hooks = require('node:async_hooks')
用語
非同期リソースは、関連付けられたコールバックを持つオブジェクトを表します。このコールバックは、net.createServer()
の'connection'
イベントのように複数回呼び出される場合もあれば、fs.open()
のように一度だけ呼び出される場合もあります。リソースは、コールバックが呼び出される前に閉じられることもあります。AsyncHook
はこれらの異なるケースを明示的に区別しませんが、それらをリソースという抽象的な概念として表します。
Worker
が使用されている場合、各スレッドは独立したasync_hooks
インターフェースを持ち、各スレッドは新しい一連の非同期 ID を使用します。
概要
公開 API の簡単な概要を以下に示します。
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) {}
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
callbacks
<Object> 登録するフックコールバックinit
<Function>init
コールバック。before
<Function>before
コールバック。after
<Function>after
コールバック。destroy
<Function>destroy
コールバック。promiseResolve
<Function>promiseResolve
コールバック。
戻り値: <AsyncHook> フックの無効化と有効化に使用されるインスタンス
各非同期操作の異なるライフタイムイベントに対して呼び出される関数を登録します。
コールバック init()
/before()
/after()
/destroy()
は、リソースのライフタイム中にそれぞれの非同期イベントに対して呼び出されます。
すべてのコールバックはオプションです。たとえば、リソースのクリーンアップのみを追跡する必要がある場合は、destroy
コールバックのみを渡す必要があります。callbacks
に渡すことができるすべての関数の詳細は、フックコールバック セクションを参照してください。
import { createHook } from 'node:async_hooks'
const asyncHook = createHook({
init(asyncId, type, triggerAsyncId, resource) {},
destroy(asyncId) {},
})
const async_hooks = require('node:async_hooks')
const asyncHook = async_hooks.createHook({
init(asyncId, type, triggerAsyncId, resource) {},
destroy(asyncId) {},
})
コールバックはプロトタイプチェーンを介して継承されます。
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
が再帰的に呼び出されることはありません。
import { writeFileSync } from 'node:fs'
import { format } from 'node:util'
function debug(...args) {
// AsyncHookコールバック内でデバッグする際に、このような関数を使用します
writeFileSync('log.out', `${format(...args)}\n`, { flag: 'a' })
}
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
インスタンスのコールバックを有効にします。コールバックが提供されていない場合、有効化はノーオペレーションです。
AsyncHook
インスタンスはデフォルトで無効になっています。AsyncHook
インスタンスを生成直後に有効にするには、次のパターンを使用できます。
import { createHook } from 'node:async_hooks'
const hook = createHook(callbacks).enable()
const async_hooks = require('node:async_hooks')
const hook = async_hooks.createHook(callbacks).enable()
asyncHook.disable()
- 戻り値: <AsyncHook>
asyncHook
への参照。
実行される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
を呼び出す必要があるという意味ではありません。可能性が存在するという意味だけです。
この動作は、リソースを開いてから、リソースを使用する前に閉じることによって観察できます。次のスニペットはこのことを示しています。
import { createServer } from 'node:net'
createServer().listen(function () {
this.close()
})
// OR
clearTimeout(setTimeout(() => {}, 10))
require('node:net')
.createServer()
.listen(function () {
this.close()
})
// OR
clearTimeout(setTimeout(() => {}, 10))
新しいリソースごとに、現在の Node.js インスタンスのスコープ内で一意の ID が割り当てられます。
type
type
は、init
の呼び出しの原因となったリソースの種類を識別する文字列です。一般的に、リソースのコンストラクタ名に対応します。
Node.js 自身によって作成されたリソースのtype
は、任意の Node.js リリースで変更される可能性があります。有効な値には、TLSWRAP
、TCPWRAP
、TCPSERVERWRAP
、GETADDRINFOREQWRAP
、FSREQCALLBACK
、Microtask
、Timeout
などがあります。完全なリストを取得するには、使用している Node.js のバージョンのソースコードを調べます。
さらに、AsyncResource
を使用するユーザーは、Node.js 自体とは独立して非同期リソースを作成します。
Promise
インスタンスとそれらによってスケジュールされた非同期作業を追跡するために使用されるPROMISE
リソースタイプもあります。
ユーザーは、公開エンベッダー API を使用するときに独自のtype
を定義できます。
型名の衝突が発生する可能性があります。フックをリッスンする際に衝突を防ぐために、エンベッダーは npm パッケージ名などの固有のプレフィックスを使用することをお勧めします。
triggerAsyncId
triggerAsyncId
は、新しいリソースの初期化(または「トリガー」)の原因となったリソースのasyncId
であり、init
の呼び出しの原因となったものです。これは、リソースが作成された時点のみを示すasync_hooks.executionAsyncId()
とは異なり、triggerAsyncId
はリソースが作成された理由を示します。
triggerAsyncId
の簡単なデモを以下に示します。
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)
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
でサーバーにアクセスしたときの出力:
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
の方がこのユースケースには適しています。
以下は、before
とafter
の呼び出し間のinit
への呼び出しに関する追加情報、特にlisten()
へのコールバックがどのように見えるかについての例です。呼び出しコンテキストを見やすくするために、出力フォーマットを少し詳しくしています。
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)
})
サーバーの起動のみからの出力:
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
はそれぞれ現在の実行コンテキストの値を指定します。これはbefore
とafter
の呼び出しによって区切られています。
execution
のみを使用してリソース割り当てのグラフを作成すると、次のようになります。
root(1)
^
|
TickObject(6)
^
|
Timeout(7)
console.log()
が呼び出された理由であるにもかかわらず、TCPSERVERWRAP
はこのグラフの一部ではありません。これは、ホスト名のないポートへのバインディングが同期操作であるためですが、完全に非同期 API を維持するために、ユーザーのコールバックはprocess.nextTick()
に配置されます。そのため、出力にTickObject
が存在し、.listen()
コールバックの「親」となっています。
このグラフはリソースがいつ作成されたかを示しているだけで、なぜ作成されたかを示していません。そのため、なぜを追跡するにはtriggerAsyncId
を使用します。これは次のグラフで表すことができます。
bootstrap(1)
|
˅
TCPSERVERWRAP(5)
|
˅
TickObject(6)
|
˅
Timeout(7)
before(asyncId)
asyncId
<number>
非同期操作が開始された時(TCP サーバーが新しい接続を受信するなど)または完了した時(ディスクへのデータ書き込みなど)、コールバックが呼び出されてユーザーに通知されます。before
コールバックは、そのコールバックが実行される直前に呼び出されます。asyncId
は、コールバックを実行しようとしているリソースに割り当てられた一意の識別子です。
before
コールバックは 0 ~ N 回呼び出されます。非同期操作がキャンセルされた場合、またはたとえば TCP サーバーが接続を受信しなかった場合は、before
コールバックは通常 0 回呼び出されます。TCP サーバーのような永続的な非同期リソースは、通常before
コールバックを複数回呼び出しますが、fs.open()
のような他の操作は一度だけ呼び出します。
after(asyncId)
asyncId
<number>
before
で指定されたコールバックが完了した直後に呼び出されます。
コールバックの実行中にキャッチされない例外が発生した場合、after
は'uncaughtException'
イベントが発行された後、またはdomain
のハンドラーが実行された後に実行されます。
destroy(asyncId)
asyncId
<number>
asyncId
に対応するリソースが破棄された後に呼び出されます。また、埋め込み API emitDestroy()
から非同期的に呼び出されます。
一部のリソースはクリーンアップにガベージコレクションに依存するため、init
に渡された resource
オブジェクトへの参照が作成されている場合、destroy
が呼び出されない可能性があり、アプリケーションでメモリリークが発生する可能性があります。リソースがガベージコレクションに依存しない場合は、これは問題になりません。
destroy フックを使用すると、ガベージコレクターを介してPromise
インスタンスの追跡が可能になるため、追加のオーバーヘッドが発生します。
promiseResolve(asyncId)
追加されたバージョン: v8.6.0
asyncId
<number>
Promise
コンストラクターに渡されたresolve
関数が呼び出されたときに呼び出されます(直接、または Promise を解決する他の手段を通して)。
resolve()
は、観測可能な同期的な作業を一切行いません。
Promise
が別のPromise
の状態を仮定して解決された場合、この時点ではPromise
が必ずしも履行または拒否されるとは限りません。
new Promise(resolve => resolve(true)).then(a => {})
は、次のコールバックを呼び出します。
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()
を使用すると、使用できるハンドルまたはリクエストオブジェクトがないため空のオブジェクトが返されますが、トップレベルを表すオブジェクトがあると便利です。
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
})
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
を使用せずに、継続ローカルストレージを実装するために使用できます。
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)
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.0 | currentId から名前変更 |
v8.1.0 | 追加: v8.1.0 |
- 戻り値: <number> 現在の実行コンテキストの
asyncId
。何かが呼び出された時点を追跡するのに役立ちます。
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()
})
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()
で扱われる)ではなく、実行タイミングに関連しています。
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。
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 が取得されないことを意味します。
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 の追跡が可能になりました。上記の例には 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
に移動しました。