Skip to content

Asynchrone Hooks

[Stabil: 1 - Experimentell]

Stabil: 1 Stabilität: 1 - Experimentell. Bitte migrieren Sie von dieser API weg, wenn möglich. Wir empfehlen die Verwendung der APIs createHook, AsyncHook und executionAsyncResource nicht, da sie Usability-Probleme, Sicherheitsrisiken und Performance-Auswirkungen haben. Anwendungsfälle für die Nachverfolgung des asynchronen Kontextes werden besser durch die stabile API AsyncLocalStorage abgedeckt. Wenn Sie einen Anwendungsfall für createHook, AsyncHook oder executionAsyncResource haben, der über die Kontextverfolgung hinausgeht, die durch AsyncLocalStorage oder die derzeit von Diagnostics Channel bereitgestellten Diagnosedaten gelöst wird, öffnen Sie bitte ein Issue unter https://github.com/nodejs/node/issues, in dem Sie Ihren Anwendungsfall beschreiben, damit wir eine gezieltere API erstellen können.

Quellcode: lib/async_hooks.js

Wir raten dringend von der Verwendung der async_hooks-API ab. Andere APIs, die die meisten ihrer Anwendungsfälle abdecken können, sind:

Das Modul node:async_hooks bietet eine API zum Verfolgen asynchroner Ressourcen. Es kann wie folgt zugegriffen werden:

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

Terminologie

Eine asynchrone Ressource repräsentiert ein Objekt mit einem zugeordneten Callback. Dieser Callback kann mehrmals aufgerufen werden, z. B. das Ereignis 'connection' in net.createServer(), oder nur einmalig, wie in fs.open(). Eine Ressource kann auch geschlossen werden, bevor der Callback aufgerufen wird. AsyncHook unterscheidet diese verschiedenen Fälle nicht explizit, sondern stellt sie als abstraktes Konzept dar, das eine Ressource ist.

Wenn Worker verwendet werden, hat jeder Thread eine unabhängige async_hooks-Schnittstelle, und jeder Thread verwendet einen neuen Satz asynchroner IDs.

Übersicht

Im Folgenden finden Sie eine einfache Übersicht über die öffentliche API.

js
import async_hooks from 'node:async_hooks'

// Gibt die ID des aktuellen Ausführungskontexts zurück.
const eid = async_hooks.executionAsyncId()

// Gibt die ID des Handles zurück, das für die Auslösung des Callbacks des
// aktuellen Ausführungsbereichs verantwortlich ist.
const tid = async_hooks.triggerAsyncId()

// Erstellt eine neue AsyncHook-Instanz. Alle diese Callbacks sind optional.
const asyncHook = async_hooks.createHook({ init, before, after, destroy, promiseResolve })

// Ermöglicht das Aufrufen von Callbacks dieser AsyncHook-Instanz. Dies ist keine
// implizite Aktion nach der Ausführung des Konstruktors und muss explizit
// ausgeführt werden, um mit der Ausführung von Callbacks zu beginnen.
asyncHook.enable()

// Deaktiviert das Abhören neuer asynchroner Ereignisse.
asyncHook.disable()

//
// Im Folgenden sind die Callbacks aufgeführt, die an createHook() übergeben
// werden können.
//

// init() wird während der Objekterstellung aufgerufen. Die Ressource ist
// möglicherweise noch nicht vollständig erstellt, wenn dieser Callback
// ausgeführt wird. Daher sind möglicherweise nicht alle Felder der Ressource,
// auf die von "asyncId" verwiesen wird, gefüllt.
function init(asyncId, type, triggerAsyncId, resource) {}

// before() wird kurz bevor der Callback der Ressource aufgerufen wird,
// aufgerufen. Er kann 0-N mal für Handles (z. B. TCPWrap) aufgerufen werden
// und wird genau 1 mal für Anforderungen (z. B. FSReqCallback) aufgerufen.
function before(asyncId) {}

// after() wird kurz nachdem der Callback der Ressource beendet wurde, aufgerufen.
function after(asyncId) {}

// destroy() wird aufgerufen, wenn die Ressource zerstört wird.
function destroy(asyncId) {}

// promiseResolve() wird nur für Promise-Ressourcen aufgerufen, wenn die
// resolve()-Funktion, die an den Promise-Konstruktor übergeben wurde, aufgerufen
// wird (entweder direkt oder durch andere Mittel zum Auflösen eines Promise).
function promiseResolve(asyncId) {}
js
const async_hooks = require('node:async_hooks')

// Gibt die ID des aktuellen Ausführungskontexts zurück.
const eid = async_hooks.executionAsyncId()

// Gibt die ID des Handles zurück, das für die Auslösung des Callbacks des
// aktuellen Ausführungsbereichs verantwortlich ist.
const tid = async_hooks.triggerAsyncId()

// Erstellt eine neue AsyncHook-Instanz. Alle diese Callbacks sind optional.
const asyncHook = async_hooks.createHook({ init, before, after, destroy, promiseResolve })

// Ermöglicht das Aufrufen von Callbacks dieser AsyncHook-Instanz. Dies ist keine
// implizite Aktion nach der Ausführung des Konstruktors und muss explizit
// ausgeführt werden, um mit der Ausführung von Callbacks zu beginnen.
asyncHook.enable()

// Deaktiviert das Abhören neuer asynchroner Ereignisse.
asyncHook.disable()

//
// Im Folgenden sind die Callbacks aufgeführt, die an createHook() übergeben
// werden können.
//

// init() wird während der Objekterstellung aufgerufen. Die Ressource ist
// möglicherweise noch nicht vollständig erstellt, wenn dieser Callback
// ausgeführt wird. Daher sind möglicherweise nicht alle Felder der Ressource,
// auf die von "asyncId" verwiesen wird, gefüllt.
function init(asyncId, type, triggerAsyncId, resource) {}

// before() wird kurz bevor der Callback der Ressource aufgerufen wird,
// aufgerufen. Er kann 0-N mal für Handles (z. B. TCPWrap) aufgerufen werden
// und wird genau 1 mal für Anforderungen (z. B. FSReqCallback) aufgerufen.
function before(asyncId) {}

// after() wird kurz nachdem der Callback der Ressource beendet wurde, aufgerufen.
function after(asyncId) {}

// destroy() wird aufgerufen, wenn die Ressource zerstört wird.
function destroy(asyncId) {}

// promiseResolve() wird nur für Promise-Ressourcen aufgerufen, wenn die
// resolve()-Funktion, die an den Promise-Konstruktor übergeben wurde, aufgerufen
// wird (entweder direkt oder durch andere Mittel zum Auflösen eines Promise).
function promiseResolve(asyncId) {}

async_hooks.createHook(callbacks)

Hinzugefügt in: v8.1.0

Registriert Funktionen, die für verschiedene Lebenszyklusereignisse jeder asynchronen Operation aufgerufen werden.

Die Callbacks init()/before()/after()/destroy() werden für das jeweilige asynchrone Ereignis während der Lebensdauer einer Ressource aufgerufen.

Alle Callbacks sind optional. Wenn beispielsweise nur die Bereinigung von Ressourcen verfolgt werden muss, muss nur der destroy-Callback übergeben werden. Die Einzelheiten aller Funktionen, die an callbacks übergeben werden können, finden Sie im Abschnitt Hook-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) {},
})

Die Callbacks werden über die Prototypenkette vererbt:

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

Da Promises asynchrone Ressourcen sind, deren Lebenszyklus über den Async-Hooks-Mechanismus verfolgt wird, dürfen die Callbacks init(), before(), after() und destroy() keine asynchronen Funktionen sein, die Promises zurückgeben.

Fehlerbehandlung

Wenn ein AsyncHook-Callback einen Fehler auslöst, gibt die Anwendung den Stack-Trace aus und beendet sich. Der Beendigungsvorgang entspricht dem einer nicht abgefangenen Ausnahme, aber alle 'uncaughtException'-Listener werden entfernt, wodurch der Prozess gezwungen wird, sich zu beenden. Die 'exit'-Callbacks werden weiterhin aufgerufen, es sei denn, die Anwendung wird mit --abort-on-uncaught-exception ausgeführt. In diesem Fall wird ein Stack-Trace ausgegeben und die Anwendung beendet sich, wobei eine Core-Datei hinterlassen wird.

Der Grund für dieses Fehlerbehandlungsverhalten ist, dass diese Callbacks an potenziell volatilen Punkten im Lebenszyklus eines Objekts ausgeführt werden, beispielsweise während der Erstellung und Zerstörung von Klassen. Aus diesem Grund wird es als notwendig erachtet, den Prozess schnell zu beenden, um einen unbeabsichtigten Abbruch in der Zukunft zu verhindern. Dies kann sich in Zukunft ändern, wenn eine umfassende Analyse durchgeführt wird, um sicherzustellen, dass eine Ausnahme dem normalen Kontrollfluss folgen kann, ohne unbeabsichtigte Nebeneffekte zu verursachen.

Ausgabe in AsyncHook-Callbacks

Da die Ausgabe auf der Konsole eine asynchrone Operation ist, führt console.log() dazu, dass AsyncHook-Callbacks aufgerufen werden. Die Verwendung von console.log() oder ähnlichen asynchronen Operationen innerhalb einer AsyncHook-Callback-Funktion führt zu einer unendlichen Rekursion. Eine einfache Lösung hierfür beim Debugging ist die Verwendung einer synchronen Protokollierungsoperation wie fs.writeFileSync(file, msg, flag). Dies gibt in die Datei aus und ruft AsyncHook nicht rekursiv auf, da es synchron ist.

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

function debug(...args) {
  // Verwenden Sie eine Funktion wie diese beim Debuggen innerhalb eines AsyncHook-Callbacks
  writeFileSync('log.out', `${format(...args)}\n`, { flag: 'a' })
}
js
const fs = require('node:fs')
const util = require('node:util')

function debug(...args) {
  // Verwenden Sie eine Funktion wie diese beim Debuggen innerhalb eines AsyncHook-Callbacks
  fs.writeFileSync('log.out', `${util.format(...args)}\n`, { flag: 'a' })
}

Wenn eine asynchrone Operation zum Protokollieren benötigt wird, ist es möglich, mithilfe der von AsyncHook selbst bereitgestellten Informationen zu verfolgen, was die asynchrone Operation verursacht hat. Die Protokollierung sollte dann übersprungen werden, wenn die Protokollierung selbst den Aufruf des AsyncHook-Callbacks verursacht hat. Dadurch wird die sonst unendliche Rekursion unterbrochen.

Klasse: AsyncHook

Die Klasse AsyncHook bietet eine Schnittstelle zum Verfolgen von Lebenszyklusereignissen asynchroner Operationen.

asyncHook.enable()

Aktiviert die Callbacks für eine gegebene AsyncHook-Instanz. Wenn keine Callbacks bereitgestellt werden, ist das Aktivieren ein No-op.

Die AsyncHook-Instanz ist standardmäßig deaktiviert. Wenn die AsyncHook-Instanz unmittelbar nach der Erstellung aktiviert werden soll, kann das folgende Muster verwendet werden.

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

Deaktiviert die Callbacks für eine gegebene AsyncHook-Instanz aus dem globalen Pool von AsyncHook-Callbacks, die ausgeführt werden sollen. Sobald ein Hook deaktiviert wurde, wird er nicht mehr aufgerufen, bis er wieder aktiviert wird.

Aus Gründen der API-Konsistenz gibt disable() auch die AsyncHook-Instanz zurück.

Hook-Callbacks

Wichtige Ereignisse im Lebenszyklus asynchroner Ereignisse wurden in vier Bereiche unterteilt: Instanziierung, vor/nach dem Aufruf des Callbacks und beim Zerstören der Instanz.

init(asyncId, type, triggerAsyncId, resource)

  • asyncId <number> Eine eindeutige ID für die asynchrone Ressource.
  • type <string> Der Typ der asynchronen Ressource.
  • triggerAsyncId <number> Die eindeutige ID der asynchronen Ressource, in deren Ausführungskontext diese asynchrone Ressource erstellt wurde.
  • resource <Object> Verweis auf die Ressource, die die asynchrone Operation darstellt, muss während destroy freigegeben werden.

Wird aufgerufen, wenn eine Klasse konstruiert wird, die die Möglichkeit hat, ein asynchrones Ereignis auszulösen. Dies bedeutet nicht, dass die Instanz before/after aufrufen muss, bevor destroy aufgerufen wird, sondern nur, dass die Möglichkeit besteht.

Dieses Verhalten kann beobachtet werden, indem man beispielsweise eine Ressource öffnet und dann schließt, bevor die Ressource verwendet werden kann. Das folgende Snippet veranschaulicht dies.

js
import { createServer } from 'node:net'

createServer().listen(function () {
  this.close()
})
// ODER
clearTimeout(setTimeout(() => {}, 10))
js
require('node:net')
  .createServer()
  .listen(function () {
    this.close()
  })
// ODER
clearTimeout(setTimeout(() => {}, 10))

Jede neue Ressource erhält eine ID, die innerhalb des Gültigkeitsbereichs der aktuellen Node.js-Instanz eindeutig ist.

type

Der type ist eine Zeichenkette, die den Typ der Ressource identifiziert, die den Aufruf von init verursacht hat. Im Allgemeinen entspricht er dem Namen des Konstruktors der Ressource.

Der type von Ressourcen, die von Node.js selbst erstellt werden, kann sich in jeder Node.js-Version ändern. Gültige Werte sind unter anderem TLSWRAP, TCPWRAP, TCPSERVERWRAP, GETADDRINFOREQWRAP, FSREQCALLBACK, Microtask und Timeout. Untersuchen Sie den Quellcode der verwendeten Node.js-Version, um die vollständige Liste zu erhalten.

Darüber hinaus erstellen Benutzer von AsyncResource asynchrone Ressourcen unabhängig von Node.js selbst.

Es gibt auch den Ressourcentyp PROMISE, der verwendet wird, um Promise-Instanzen und von ihnen geplante asynchrone Arbeiten zu verfolgen.

Benutzer können ihren eigenen type definieren, wenn sie die öffentliche Embedder-API verwenden.

Es ist möglich, dass Namenskollisionen bei den Typen auftreten. Embedder werden ermutigt, eindeutige Präfixe zu verwenden, z. B. den npm-Paketnamen, um Kollisionen beim Abhören der Hooks zu vermeiden.

triggerAsyncId

triggerAsyncId ist die asyncId der Ressource, die die Initialisierung der neuen Ressource verursacht (oder "ausgelöst") hat und die den Aufruf von init verursacht hat. Dies unterscheidet sich von async_hooks.executionAsyncId(), das nur zeigt, wann eine Ressource erstellt wurde, während triggerAsyncId zeigt, warum eine Ressource erstellt wurde.

Im Folgenden finden Sie eine einfache Demonstration von 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)

Ausgabe beim Aufrufen des Servers mit nc localhost 8080:

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

Das TCPSERVERWRAP ist der Server, der die Verbindungen empfängt.

Das TCPWRAP ist die neue Verbindung vom Client. Wenn eine neue Verbindung hergestellt wird, wird die TCPWrap-Instanz sofort erstellt. Dies geschieht außerhalb jedes JavaScript-Stacks. (Eine executionAsyncId() von 0 bedeutet, dass sie von C++ ohne darüber liegenden JavaScript-Stack ausgeführt wird.) Mit diesen Informationen allein wäre es unmöglich, Ressourcen in Bezug darauf zu verknüpfen, was ihre Erstellung verursacht hat, daher erhält triggerAsyncId die Aufgabe, zu verbreiten, welche Ressource für die Existenz der neuen Ressource verantwortlich ist.

resource

resource ist ein Objekt, das die tatsächlich initialisierte asynchrone Ressource repräsentiert. Die API zum Zugriff auf das Objekt kann vom Ersteller der Ressource spezifiziert werden. Von Node.js selbst erstellte Ressourcen sind intern und können sich jederzeit ändern. Daher ist für diese keine API spezifiziert.

In einigen Fällen wird das Ressourcenobjekt aus Performance-Gründen wiederverwendet. Es ist daher nicht sicher, es als Schlüssel in einer WeakMap zu verwenden oder ihm Eigenschaften hinzuzufügen.

Beispiel für asynchronen Kontext

Der Anwendungsfall der Kontextverfolgung wird durch die stabile API AsyncLocalStorage abgedeckt. Dieses Beispiel veranschaulicht nur die Funktionsweise von asynchronen Hooks, aber AsyncLocalStorage passt besser zu diesem Anwendungsfall.

Das Folgende ist ein Beispiel mit zusätzlichen Informationen über die Aufrufe von init zwischen den before- und after-Aufrufen, insbesondere wie der Rückruf an listen() aussehen wird. Die Ausgabeformatierung ist etwas ausführlicher gestaltet, um den Aufrufkontext besser sichtbar zu machen.

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

Ausgabe nur beim Starten des Servers:

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

Wie im Beispiel dargestellt, geben executionAsyncId() und execution jeweils den Wert des aktuellen Ausführungskontexts an; dieser wird durch Aufrufe von before und after abgegrenzt.

Nur die Verwendung von execution zum Erstellen eines Graphen der Ressourcenzuweisung führt zu folgendem Ergebnis:

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

Der TCPSERVERWRAP ist nicht Teil dieses Graphen, obwohl er der Grund für den Aufruf von console.log() war. Dies liegt daran, dass die Bindung an einen Port ohne Hostnamen eine synchron Operation ist, aber um eine vollständig asynchrone API aufrechtzuerhalten, wird der Rückruf des Benutzers in einen process.nextTick() gestellt. Deshalb ist TickObject in der Ausgabe vorhanden und ein 'Elternteil' für den .listen()-Rückruf.

Der Graph zeigt nur wann eine Ressource erstellt wurde, nicht warum. Um das Warum zu verfolgen, verwenden Sie triggerAsyncId. Dies kann mit dem folgenden Graphen dargestellt werden:

bash
 bootstrap(1)
     |
     ˅
TCPSERVERWRAP(5)
     |
     ˅
 TickObject(6)
     |
     ˅
  Timeout(7)

before(asyncId)

Wenn eine asynchrone Operation initiiert wird (z. B. ein TCP-Server eine neue Verbindung empfängt) oder abgeschlossen wird (z. B. das Schreiben von Daten auf die Festplatte), wird ein Callback aufgerufen, um den Benutzer zu benachrichtigen. Der before-Callback wird kurz vor der Ausführung des genannten Callbacks aufgerufen. asyncId ist die eindeutige Kennung, die der Ressource zugewiesen wird, die den Callback ausführen wird.

Der before-Callback wird 0 bis N Mal aufgerufen. Der before-Callback wird typischerweise 0 Mal aufgerufen, wenn die asynchrone Operation abgebrochen wurde oder beispielsweise keine Verbindungen von einem TCP-Server empfangen werden. Persistente asynchrone Ressourcen wie ein TCP-Server rufen den before-Callback typischerweise mehrmals auf, während andere Operationen wie fs.open() ihn nur einmal aufrufen.

after(asyncId)

Wird unmittelbar nach dem Abschluss des in before angegebenen Callbacks aufgerufen.

Wenn während der Ausführung des Callbacks eine nicht abgefangene Ausnahme auftritt, wird after nach dem Auslösen des Ereignisses 'uncaughtException' oder der Ausführung des Handlers einer domain ausgeführt.

destroy(asyncId)

Wird aufgerufen, nachdem die Ressource, die asyncId entspricht, zerstört wurde. Es wird auch asynchron von der Embedder-API emitDestroy() aufgerufen.

Einige Ressourcen hängen für die Bereinigung von der Garbage Collection ab. Wenn also ein Verweis auf das an init übergebene resource-Objekt erstellt wird, ist es möglich, dass destroy niemals aufgerufen wird, was zu einem Speicherleck in der Anwendung führt. Wenn die Ressource nicht von der Garbage Collection abhängt, ist dies kein Problem.

Die Verwendung des destroy-Hooks führt zu zusätzlichem Overhead, da er die Verfolgung von Promise-Instanzen über den Garbage Collector ermöglicht.

promiseResolve(asyncId)

Hinzugefügt in: v8.6.0

Wird aufgerufen, wenn die an den Promise-Konstruktor übergebene resolve-Funktion aufgerufen wird (entweder direkt oder durch andere Mittel zum Auflösen eines Promises).

resolve() führt keine beobachtbare synchrone Arbeit aus.

Das Promise ist zu diesem Zeitpunkt nicht unbedingt erfüllt oder abgelehnt, wenn das Promise durch die Übernahme des Zustands eines anderen Promise aufgelöst wurde.

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

ruft die folgenden Callbacks auf:

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

Hinzugefügt in: v13.9.0, v12.17.0

  • Gibt zurück: <Objekt> Die Ressource, die die aktuelle Ausführung repräsentiert. Nützlich, um Daten innerhalb der Ressource zu speichern.

Ressourcenobjekte, die von executionAsyncResource() zurückgegeben werden, sind meist interne Node.js-Handle-Objekte mit nicht dokumentierten APIs. Die Verwendung von Funktionen oder Eigenschaften des Objekts führt wahrscheinlich zum Absturz Ihrer Anwendung und sollte vermieden werden.

Die Verwendung von executionAsyncResource() im Top-Level-Ausführungskontext gibt ein leeres Objekt zurück, da kein Handle- oder Anforderungsobjekt verwendet werden kann. Ein Objekt, das die Top-Level-Ebene repräsentiert, kann jedoch hilfreich sein.

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

Dies kann verwendet werden, um Continuation Local Storage ohne die Verwendung einer Tracking-Map zur Speicherung der Metadaten zu implementieren:

js
import { createServer } from 'node:http'
import { executionAsyncId, executionAsyncResource, createHook } from 'node:async_hooks'
const sym = Symbol('state') // Privates Symbol, um Verschmutzung zu vermeiden

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') // Privates Symbol, um Verschmutzung zu vermeiden

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

[Historie]

VersionÄnderungen
v8.2.0Umbenannt von currentId.
v8.1.0Hinzugefügt in: v8.1.0
  • Rückgabewert: <number> Die asyncId des aktuellen Ausführungskontexts. Nützlich, um zu verfolgen, wann etwas aufgerufen wird.
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()
})

Die von executionAsyncId() zurückgegebene ID bezieht sich auf das Ausführungstiming, nicht auf die Kausalität (die von triggerAsyncId() behandelt wird):

js
const server = net
  .createServer(conn => {
    // Gibt die ID des Servers zurück, nicht die der neuen Verbindung, da der
    // Callback im Ausführungsbereich des MakeCallback() des Servers ausgeführt wird.
    async_hooks.executionAsyncId()
  })
  .listen(port, () => {
    // Gibt die ID eines TickObjects (process.nextTick()) zurück, da alle
    // an .listen() übergebenen Callbacks in einem nextTick() verpackt sind.
    async_hooks.executionAsyncId()
  })

Promise-Kontexte erhalten möglicherweise standardmäßig keine präzisen executionAsyncIds. Siehe den Abschnitt zur Promise-Ausführungsverfolgung.

async_hooks.triggerAsyncId()

  • Rückgabewert: <number> Die ID der Ressource, die für den Aufruf des aktuell ausgeführten Callbacks verantwortlich ist.
js
const server = net
  .createServer(conn => {
    // Die Ressource, die diesen Callback verursacht (oder ausgelöst) hat, war die
    // der neuen Verbindung. Daher ist der Rückgabewert von triggerAsyncId()
    // die asyncId von "conn".
    async_hooks.triggerAsyncId()
  })
  .listen(port, () => {
    // Obwohl alle an .listen() übergebenen Callbacks in einem nextTick() verpackt sind,
    // existiert der Callback selbst, weil der Aufruf von .listen() des Servers
    // getätigt wurde. Daher wäre der Rückgabewert die ID des Servers.
    async_hooks.triggerAsyncId()
  })

Promise-Kontexte erhalten möglicherweise standardmäßig keine gültigen triggerAsyncIds. Siehe den Abschnitt zur Promise-Ausführungsverfolgung.

async_hooks.asyncWrapProviders

Hinzugefügt in: v17.2.0, v16.14.0

  • Rückgabewert: Eine Zuordnung von Anbietertypen zu den entsprechenden numerischen IDs. Diese Zuordnung enthält alle Ereignistypen, die vom Ereignis async_hooks.init() ausgegeben werden könnten.

Dieses Feature unterdrückt die veraltete Verwendung von process.binding('async_wrap').Providers. Siehe: DEP0111

Promise-Ausführungsverfolgung

Standardmäßig werden Promise-Ausführungen aufgrund des relativ hohen Aufwands der von V8 bereitgestellten Promise-Introspection-API keine asyncIds zugewiesen. Das bedeutet, dass Programme, die Promises oder async/await verwenden, standardmäßig keine korrekten Ausführungs- und Trigger-IDs für Promise-Callback-Kontexte erhalten.

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

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

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

Beachten Sie, dass der then()-Callback behauptet, im Kontext des äußeren Bereichs ausgeführt worden zu sein, obwohl ein asynchroner Sprung beteiligt war. Außerdem ist der Wert von triggerAsyncId 0, was bedeutet, dass der Kontext der Ressource fehlt, die den then()-Callback (getriggert) zur Ausführung gebracht hat.

Die Installation von asynchronen Hooks über async_hooks.createHook aktiviert die Promise-Ausführungsverfolgung:

js
import { createHook, executionAsyncId, triggerAsyncId } from 'node:async_hooks'
createHook({ init() {} }).enable() // erzwingt die Aktivierung von PromiseHooks.
Promise.resolve(1729).then(() => {
  console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`)
})
// Ausgabe:
// eid 7 tid 6
js
const { createHook, executionAsyncId, triggerAsyncId } = require('node:async_hooks')

createHook({ init() {} }).enable() // erzwingt die Aktivierung von PromiseHooks.
Promise.resolve(1729).then(() => {
  console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`)
})
// Ausgabe:
// eid 7 tid 6

In diesem Beispiel hat das Hinzufügen einer beliebigen Hook-Funktion die Verfolgung von Promises aktiviert. Es gibt zwei Promises im obigen Beispiel; das Promise, das von Promise.resolve() erstellt wurde, und das Promise, das von dem Aufruf von then() zurückgegeben wurde. Im obigen Beispiel erhielt das erste Promise die asyncId 6 und das letztere die asyncId 7. Während der Ausführung des then()-Callbacks befinden wir uns im Kontext des Promises mit der asyncId 7. Dieses Promise wurde von der asynchronen Ressource 6 ausgelöst.

Eine weitere Feinheit bei Promises ist, dass before- und after-Callbacks nur bei verketteten Promises ausgeführt werden. Das bedeutet, dass Promises, die nicht von then()/catch() erstellt wurden, keine before- und after-Callbacks auslösen. Weitere Einzelheiten finden Sie in den Details der V8 PromiseHooks-API.

JavaScript Embedder-API

Bibliotheksentwickler, die ihre eigenen asynchronen Ressourcen verwalten und Aufgaben wie I/O, Verbindungspooling oder die Verwaltung von Callback-Warteschlangen durchführen, können die AsyncResource-JavaScript-API verwenden, damit alle entsprechenden Callbacks aufgerufen werden.

Klasse: AsyncResource

Die Dokumentation für diese Klasse wurde verschoben AsyncResource.

Klasse: AsyncLocalStorage

Die Dokumentation für diese Klasse wurde verschoben AsyncLocalStorage.