Async hooks#

安定性: 1 - 実験的。可能であれば、このAPIからの移行をお願いします。 createHook, AsyncHook, および executionAsyncResource APIは、使いやすさの問題、安全性のリスク、パフォーマンスへの影響があるため、使用を推奨しません。非同期コンテキスト追跡のユースケースは、安定版の AsyncLocalStorage APIでより良く対応できます。 AsyncLocalStorage や現在 Diagnostics Channel で提供されている診断データで解決されるコンテキスト追跡のニーズを超えた createHook, AsyncHook, または executionAsyncResource のユースケースがある場合は、https://github.com/nodejs/node/issues にて、より目的に特化したAPIを作成できるよう、ユースケースを説明するissueをオープンしてください。

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

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

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 インターフェースを持ち、各スレッドは新しい async ID のセットを使用します。

概要#

以下は、公開APIの簡単な概要です。

import async_hooks from 'node:async_hooks';

// Return the ID of the current execution context.
const eid = async_hooks.executionAsyncId();

// Return the ID of the handle responsible for triggering the callback of the
// current execution scope to call.
const tid = async_hooks.triggerAsyncId();

// Create a new AsyncHook instance. All of these callbacks are optional.
const asyncHook =
    async_hooks.createHook({ init, before, after, destroy, promiseResolve });

// Allow callbacks of this AsyncHook instance to call. This is not an implicit
// action after running the constructor, and must be explicitly run to begin
// executing callbacks.
asyncHook.enable();

// Disable listening for new asynchronous events.
asyncHook.disable();

//
// The following are the callbacks that can be passed to createHook().
//

// init() is called during object construction. The resource may not have
// completed construction when this callback runs. Therefore, all fields of the
// resource referenced by "asyncId" may not have been populated.
function init(asyncId, type, triggerAsyncId, resource) { }

// before() is called just before the resource's callback is called. It can be
// called 0-N times for handles (such as TCPWrap), and will be called exactly 1
// time for requests (such as FSReqCallback).
function before(asyncId) { }

// after() is called just after the resource's callback has finished.
function after(asyncId) { }

// destroy() is called when the resource is destroyed.
function destroy(asyncId) { }

// promiseResolve() is called only for promise resources, when the
// resolve() function passed to the Promise constructor is invoked
// (either directly or through other means of resolving a promise).
function promiseResolve(asyncId) { }const async_hooks = require('node:async_hooks');

// Return the ID of the current execution context.
const eid = async_hooks.executionAsyncId();

// Return the ID of the handle responsible for triggering the callback of the
// current execution scope to call.
const tid = async_hooks.triggerAsyncId();

// Create a new AsyncHook instance. All of these callbacks are optional.
const asyncHook =
    async_hooks.createHook({ init, before, after, destroy, promiseResolve });

// Allow callbacks of this AsyncHook instance to call. This is not an implicit
// action after running the constructor, and must be explicitly run to begin
// executing callbacks.
asyncHook.enable();

// Disable listening for new asynchronous events.
asyncHook.disable();

//
// The following are the callbacks that can be passed to createHook().
//

// init() is called during object construction. The resource may not have
// completed construction when this callback runs. Therefore, all fields of the
// resource referenced by "asyncId" may not have been populated.
function init(asyncId, type, triggerAsyncId, resource) { }

// before() is called just before the resource's callback is called. It can be
// called 0-N times for handles (such as TCPWrap), and will be called exactly 1
// time for requests (such as FSReqCallback).
function before(asyncId) { }

// after() is called just after the resource's callback has finished.
function after(asyncId) { }

// destroy() is called when the resource is destroyed.
function destroy(asyncId) { }

// promiseResolve() is called only for promise resources, when the
// resolve() function passed to the Promise constructor is invoked
// (either directly or through other means of resolving a promise).
function promiseResolve(asyncId) { }

async_hooks.createHook(callbacks)#

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

コールバック 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は、async hooksメカニズムを介してライフサイクルが追跡される非同期リソースであるため、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) {
  // Use a function like this one when debugging inside an AsyncHook callback
  writeFileSync('log.out', `${format(...args)}\n`, { flag: 'a' });
}const fs = require('node:fs');
const util = require('node:util');

function debug(...args) {
  // Use a function like this one when debugging inside an AsyncHook callback
  fs.writeFileSync('log.out', `${util.format(...args)}\n`, { flag: 'a' });
}

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

クラス: AsyncHook#

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

asyncHook.enable()#

指定された AsyncHook インスタンスのコールバックを有効にします。コールバックが提供されていない場合、有効化は何もしません (no-op)。

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 インスタンスのコールバックを無効にします。フックが無効になると、有効になるまで再度呼び出されることはありません。

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 は、新しいリソースの初期化を引き起こし(または「トリガーし」)、init の呼び出しを引き起こしたリソースの asyncId です。これは、リソースがいつ作成されたかだけを示す 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 であることは、それがC++から実行されており、その上にJavaScriptスタックがないことを意味します。) その情報だけでは、何がリソースの作成を引き起こしたかという点でリソースをリンクすることは不可能です。そのため、triggerAsyncId は新しいリソースの存在に責任があるリソースを伝播するタスクを与えられています。

resource#

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

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

非同期コンテキストの例#

コンテキスト追跡のユースケースは、安定版APIのAsyncLocalStorageによってカバーされています。この例はasync hooksの操作を説明するためだけのものであり、このユースケースにはAsyncLocalStorageの方が適しています。

以下は、beforeafter の呼び出しの間の 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 はそれぞれ現在の実行コンテキストの値を示します。これは beforeafter の呼び出しによって区切られます。

リソース割り当てをグラフ化するために 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)#

非同期操作が開始されたとき(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)#

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

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

Promise が別の Promise の状態を引き継いで解決された場合、この時点では必ずしも fulfilled または rejected になっているとは限りません。

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

  • 戻り値: <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'); // Private symbol to avoid pollution

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'); // Private symbol to avoid pollution

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

  • 戻り値: <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) => {
  // Returns the ID of the server, not of the new connection, because the
  // callback runs in the execution scope of the server's MakeCallback().
  async_hooks.executionAsyncId();

}).listen(port, () => {
  // Returns the ID of a TickObject (process.nextTick()) because all
  // callbacks passed to .listen() are wrapped in a nextTick().
  async_hooks.executionAsyncId();
}); 

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

async_hooks.triggerAsyncId()#

  • 戻り値: <number> 現在実行中のコールバックを呼び出した原因となったリソースのID。
const server = net.createServer((conn) => {
  // The resource that caused (or triggered) this callback to be called
  // was that of the new connection. Thus the return value of triggerAsyncId()
  // is the asyncId of "conn".
  async_hooks.triggerAsyncId();

}).listen(port, () => {
  // Even though all callbacks passed to .listen() are wrapped in a nextTick()
  // the callback itself exists because the call to the server's .listen()
  // was made. So the return value would be the ID of the server.
  async_hooks.triggerAsyncId();
}); 

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

async_hooks.asyncWrapProviders#

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

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

Promise実行の追跡#

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

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

Promise.resolve(1729).then(() => {
  console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`);
});
// produces:
// eid 1 tid 0const { executionAsyncId, triggerAsyncId } = require('node:async_hooks');

Promise.resolve(1729).then(() => {
  console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`);
});
// produces:
// eid 1 tid 0

非同期のホップがあったにもかかわらず、then() コールバックは外側のスコープのコンテキストで実行されたと主張していることに注意してください。また、triggerAsyncId の値は 0 であり、then() コールバックの実行を引き起こした(トリガーした)リソースに関するコンテキストが欠けていることを意味します。

async_hooks.createHook を介してasync hooksをインストールすると、Promise実行の追跡が有効になります。

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 6const { 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

この例では、実際のフック関数を追加することで、Promiseの追跡が有効になりました。上の例には2つのPromiseがあります。Promise.resolve() によって作成されたPromiseと、then() の呼び出しによって返されたPromiseです。上の例では、最初のPromiseは asyncId 6 を、後者は asyncId 7 を取得しました。then() コールバックの実行中、我々は asyncId 7 のPromiseのコンテキストで実行しています。このPromiseは非同期リソース 6 によってトリガーされました。

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

JavaScriptエンベダーAPI#

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

クラス: AsyncResource#

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

クラス: AsyncLocalStorage#

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