非同期フック#

安定性: 1 - 実験的。 可能であれば、この API からの移行をお願いします。 createHookAsyncHookexecutionAsyncResource API は、使いやすさの問題、安全上のリスク、パフォーマンスへの影響があるため、使用しないことをお勧めします。 非同期コンテキスト追跡のユースケースは、安定した AsyncLocalStorage API によってより適切に処理されます。 AsyncLocalStorage で解決されるコンテキスト追跡のニーズ、または 診断チャネル によって現在提供されている診断データを超える createHookAsyncHook、または executionAsyncResource のユースケースがある場合は、https://github.com/nodejs/node/issues にイシューを開き、ユースケースを説明して、より目的に特化した API を作成できるようにしてください。

ソースコード: 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() のように 1 回だけ呼び出される場合もあります。 また、コールバックが呼び出される前にリソースが閉じられることもあります。 AsyncHook は、これらの異なるケースを明示的に区別するのではなく、リソースという抽象的な概念として表現します。

Worker が使用されている場合、各スレッドは独立した async_hooks インターフェースを持ち、各スレッドは新しい非同期 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 は、ライフサイクルが非同期フックメカニズムによって追跡される非同期リソースであるため、init()before()after()destroy() コールバックは、Promise を返す非同期関数であってはなりません。

エラー処理#

AsyncHook コールバックがスローされた場合、アプリケーションはスタックトレースを出力して終了します。 終了パスはキャッチされていない例外のパスに従いますが、すべての 'uncaughtException' リスナーが削除されるため、プロセスは強制終了されます。 アプリケーションが --abort-on-uncaught-exception で実行されている場合を除き、'exit' コールバックは引き続き呼び出されます。 この場合、スタックトレースが出力され、アプリケーションはコアファイルを will 残して終了します。

このエラー処理動作の理由は、これらのコールバックがオブジェクトのライフタイムにおいて、たとえばクラスの構築と破棄中に、潜在的に不安定なポイントで実行されているためです。 このため、将来意図しない中止を防ぐために、プロセスを迅速に停止する必要があると判断されます。 例外が意図しない副作用なしに通常の制御フローに従うことができることを確認するための包括的な分析が実行された場合、これは将来変更される可能性があります。

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 インスタンスのコールバックを有効にします。 コールバックが提供されない場合、有効化は操作なしです。

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のリリースごとに変更される可能性があります。有効な値には、TLSWRAPTCPWRAPTCPSERVERWRAPGETADDRINFOREQWRAPFSREQCALLBACKMicrotaskTimeout などがあります。使用している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は指定されていません。

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

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

コンテキスト追跡のユースケースは、安定版API AsyncLocalStorage でカバーされています。この例は非同期フックの動作を示しているだけですが、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 はそれぞれ、現在の`実行コンテキストの値を指定します。これは、`before` と `after` の呼び出しによって区切られます。

リソース割り当てのグラフ化に execution のみを使用すると、次のようになります。

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

TCPSERVERWRAP は、console.log() が呼び出された理由であったにもかかわらず、このグラフの一部ではありません。これは、ホスト名なしでポートにバインドすることは*同期*操作ですが、完全に非同期の 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()` などの他の操作は 1 回だけ呼び出します。

after(asyncId)#

before で指定されたコールバックが完了した直後に呼び出されます。

コールバックの実行中にキャッチされない例外が発生した場合、after'uncaughtException' イベントが発行された後、または domain のハンドラーが実行されたに実行されます。

destroy(asyncId)#

asyncId に対応するリソースが破棄された後に呼び出されます。また、エンベダー API `emitDestroy()` から非同期的に呼び出されます。

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

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

promiseResolve(asyncId)#

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

  • 戻り値: <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 '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 コンテキストは、デフォルトでは正確な `executionAsyncId` を取得できない場合があります。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` を介して非同期フックをインストールすると、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の追跡が可能になります。上記の例には、`Promise.resolve()` によって作成されたPromiseと、`then()` の呼び出しによって返されたPromiseの2つのPromiseがあります。上記の例では、最初のPromiseは `asyncId` `6` を取得し、後者は `asyncId` `7` を取得しました。 `then()` コールバックの実行中は、`asyncId` `7` を持つPromiseのコンテキストで実行しています。このPromiseは、非同期リソース `6` によってトリガーされました。

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

JavaScript エンベダー API#

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

クラス: `AsyncResource`#

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

クラス: `AsyncLocalStorage`#

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