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:
AsyncLocalStorage
verfolgt den asynchronen Kontextprocess.getActiveResourcesInfo()
verfolgt aktive Ressourcen
Das Modul node:async_hooks
bietet eine API zum Verfolgen asynchroner Ressourcen. Es kann wie folgt zugegriffen werden:
import async_hooks from 'node:async_hooks'
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.
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) {}
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
callbacks
<Object> Die Hook-Callbacks zur Registrierunginit
<Function> Derinit
-Callback.before
<Function> Derbefore
-Callback.after
<Function> Derafter
-Callback.destroy
<Function> Derdestroy
-Callback.promiseResolve
<Function> DerpromiseResolve
-Callback.
Rückgabewert: <AsyncHook> Instanz zum Deaktivieren und Aktivieren von Hooks
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.
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) {},
})
Die Callbacks werden über die Prototypenkette vererbt:
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.
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' })
}
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()
- Rückgabewert: <AsyncHook> Ein Verweis auf
asyncHook
.
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.
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()
- Rückgabewert: <AsyncHook> Ein Verweis auf
asyncHook
.
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.
import { createServer } from 'node:net'
createServer().listen(function () {
this.close()
})
// ODER
clearTimeout(setTimeout(() => {}, 10))
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
:
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)
Ausgabe beim Aufrufen des Servers mit nc localhost 8080
:
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.
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)
})
Ausgabe nur beim Starten des Servers:
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:
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:
bootstrap(1)
|
˅
TCPSERVERWRAP(5)
|
˅
TickObject(6)
|
˅
Timeout(7)
before(asyncId)
asyncId
<number>
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)
asyncId
<number>
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)
asyncId
<number>
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
asyncId
<number>
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.
new Promise(resolve => resolve(true)).then(a => {})
ruft die folgenden Callbacks auf:
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.
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
})
Dies kann verwendet werden, um Continuation Local Storage ohne die Verwendung einer Tracking-Map
zur Speicherung der Metadaten zu implementieren:
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)
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.0 | Umbenannt von currentId . |
v8.1.0 | Hinzugefügt in: v8.1.0 |
- Rückgabewert: <number> Die
asyncId
des aktuellen Ausführungskontexts. Nützlich, um zu verfolgen, wann etwas aufgerufen wird.
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()
})
Die von executionAsyncId()
zurückgegebene ID bezieht sich auf das Ausführungstiming, nicht auf die Kausalität (die von triggerAsyncId()
behandelt wird):
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.
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 asyncId
s 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.
import { executionAsyncId, triggerAsyncId } from 'node:async_hooks'
Promise.resolve(1729).then(() => {
console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`)
})
// Ausgabe:
// eid 1 tid 0
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:
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
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
.