非同步掛鉤#

穩定性:1 - 實驗性。如果您能,請移轉離開這個 API。我們不建議使用 createHookAsyncHookexecutionAsyncResource API,因為它們有可用性問題、安全風險和效能影響。非同步內容追蹤用例更適合使用穩定的 AsyncLocalStorage API。如果您有超出 AsyncLocalStorage 解決的內容追蹤需求或 診斷頻道 目前提供的診斷資料的 createHookAsyncHookexecutionAsyncResource 用例,請在 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()。資源也可以在呼叫回呼之前關閉。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 回呼即可。可以在 Hook 回呼 區段中找到可以傳遞給 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()); 

由於承諾是其生命週期透過非同步掛鉤機制追蹤的非同步資源,因此init()before()after()destroy()回呼函數不得是傳回承諾的非同步函數。

錯誤處理#

如果任何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 執行個體啟用回呼。如果未提供任何回呼,啟用將不會執行任何操作。

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 執行個體。

掛鉤回呼#

非同步事件生命週期中的主要事件已分類為四個領域:實例化、在回呼被呼叫之前/之後,以及當執行個體被銷毀時。

init(asyncId, type, triggerAsyncId, resource)#
  • asyncId <數字> 非同步資源的唯一 ID。
  • type <字串> 非同步資源的類型。
  • triggerAsyncId <數字> 在其執行內容中建立此非同步資源的非同步資源的唯一 ID。
  • resource <物件> 參照代表非同步作業的資源,需要在 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。

在某些情況下,資源物件會因效能因素而重複使用,因此不適合將其用作 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 各自指定目前執行內容的值;這由 beforeafter 呼叫區分。

僅使用 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() 這類的其他作業只會呼叫一次。

after(asyncId)#

before 中指定的回呼函式完成後立即呼叫。

如果在執行回呼函式期間發生未捕捉的例外狀況,則 after 會在發出 'uncaughtException' 事件或執行 domain 的處理常式之後執行。

destroy(asyncId)#

在對應於 asyncId 的資源被銷毀後呼叫。也會從嵌入器 API emitDestroy() 非同步呼叫。

有些資源依賴垃圾回收進行清理,因此如果參照傳遞給 initresource 物件,則有可能永遠不會呼叫 destroy,導致應用程式發生記憶體外洩。如果資源不依賴垃圾回收,則這不會是個問題。

使用 destroy 掛鉤會導致額外的負擔,因為它會透過垃圾回收追蹤 Promise 實例。

promiseResolve(asyncId)#

當呼叫傳遞給 Promise 建構函式的 resolve 函式時 (直接或透過其他解決承諾的方法),就會呼叫此函式。

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() 傳回的資源物件通常是內部 Node.js 處理物件,具有未記錄的 API。使用物件上的任何函式或屬性都可能會導致應用程式崩潰,應避免使用。

在頂層執行內容中使用 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 內容可能無法取得精確的 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。

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;由 Promise.resolve() 建立的 Promise,以及由呼叫 then() 回傳的 Promise。在上述範例中,第一個 Promise 取得 asyncId 6,後者取得 asyncId 7。在執行 then() 回呼期間,我們在 asyncId 7 的 Promise 內容中執行。此 Promise 由非同步資源 6 觸發。

Promise 的另一個細微差別是 beforeafter 回呼僅在鏈結 Promise 上執行。這表示並非由 then()/catch() 建立的 Promise,不會在它們上觸發 beforeafter 回呼。如需更多詳細資訊,請參閱 V8 PromiseHooks API 的詳細資訊。

JavaScript 嵌入器 API#

處理自己的非同步資源的函式庫開發人員執行 I/O、連線池或管理回呼佇列等任務,可以使用 AsyncResource JavaScript API,以便呼叫所有適當的回呼。

類別:AsyncResource#

此類別的說明文件已移至 AsyncResource

類別:AsyncLocalStorage#

此類別的說明文件已移至 AsyncLocalStorage