Hook asincroni
[Stabile: 1 - Sperimentale]
Stabile: 1 Stabilità: 1 - Sperimentale. Si prega di migrare da questa API, se possibile. Si sconsiglia l'uso delle API createHook
, AsyncHook
e executionAsyncResource
poiché presentano problemi di usabilità, rischi per la sicurezza e implicazioni sulle prestazioni. I casi d'uso per il tracciamento del contesto asincrono sono meglio gestiti dalla stabile API AsyncLocalStorage
. Se hai un caso d'uso per createHook
, AsyncHook
o executionAsyncResource
oltre all'esigenza di tracciamento del contesto risolta da AsyncLocalStorage
o dai dati diagnostici attualmente forniti da Canale Diagnostico, si prega di aprire un problema all'indirizzo https://github.com/nodejs/node/issues descrivendo il tuo caso d'uso in modo da poter creare un'API più mirata.
Codice Sorgente: lib/async_hooks.js
Sconsigliamo vivamente l'uso dell'API async_hooks
. Altre API in grado di coprire la maggior parte dei suoi casi d'uso includono:
AsyncLocalStorage
traccia il contesto asincronoprocess.getActiveResourcesInfo()
traccia le risorse attive
Il modulo node:async_hooks
fornisce un'API per tracciare le risorse asincrone. È possibile accedervi utilizzando:
import async_hooks from 'node:async_hooks'
const async_hooks = require('node:async_hooks')
Terminologia
Una risorsa asincrona rappresenta un oggetto con un callback associato. Questo callback può essere chiamato più volte, come l'evento 'connection'
in net.createServer()
, o solo una singola volta come in fs.open()
. Una risorsa può anche essere chiusa prima che il callback venga chiamato. AsyncHook
non distingue esplicitamente tra questi diversi casi, ma li rappresenterà come il concetto astratto che è una risorsa.
Se vengono utilizzati Worker
, ogni thread ha un'interfaccia async_hooks
indipendente e ogni thread utilizzerà un nuovo set di ID asincroni.
Panoramica
Di seguito è riportata una semplice panoramica dell'API pubblica.
import async_hooks from 'node:async_hooks'
// Restituisce l'ID del contesto di esecuzione corrente.
const eid = async_hooks.executionAsyncId()
// Restituisce l'ID dell'handle responsabile dell'attivazione della callback
// dell'ambito di esecuzione corrente da chiamare.
const tid = async_hooks.triggerAsyncId()
// Crea una nuova istanza di AsyncHook. Tutte queste callback sono facoltative.
const asyncHook = async_hooks.createHook({ init, before, after, destroy, promiseResolve })
// Consente alle callback di questa istanza di AsyncHook di essere chiamate. Questa non è un'azione implicita
// dopo l'esecuzione del costruttore e deve essere eseguita esplicitamente per iniziare
// l'esecuzione delle callback.
asyncHook.enable()
// Disabilita l'ascolto di nuovi eventi asincroni.
asyncHook.disable()
//
// Di seguito sono riportate le callback che possono essere passate a createHook().
//
// init() viene chiamato durante la costruzione dell'oggetto. La risorsa potrebbe non aver
// completato la costruzione quando viene eseguita questa callback. Pertanto, tutti i campi del
// risorsa a cui fa riferimento "asyncId" potrebbero non essere stati popolati.
function init(asyncId, type, triggerAsyncId, resource) {}
// before() viene chiamato appena prima che venga chiamata la callback della risorsa. Può essere
// chiamato 0-N volte per gli handle (come TCPWrap) e verrà chiamato esattamente 1
// volta per le richieste (come FSReqCallback).
function before(asyncId) {}
// after() viene chiamato subito dopo che la callback della risorsa ha terminato.
function after(asyncId) {}
// destroy() viene chiamato quando la risorsa viene distrutta.
function destroy(asyncId) {}
// promiseResolve() viene chiamato solo per le risorse promise, quando la
// funzione resolve() passata al costruttore Promise viene invocata
// (direttamente o tramite altri mezzi per risolvere una promise).
function promiseResolve(asyncId) {}
const async_hooks = require('node:async_hooks')
// Restituisce l'ID del contesto di esecuzione corrente.
const eid = async_hooks.executionAsyncId()
// Restituisce l'ID dell'handle responsabile dell'attivazione della callback
// dell'ambito di esecuzione corrente da chiamare.
const tid = async_hooks.triggerAsyncId()
// Crea una nuova istanza di AsyncHook. Tutte queste callback sono facoltative.
const asyncHook = async_hooks.createHook({ init, before, after, destroy, promiseResolve })
// Consente alle callback di questa istanza di AsyncHook di essere chiamate. Questa non è un'azione implicita
// dopo l'esecuzione del costruttore e deve essere eseguita esplicitamente per iniziare
// l'esecuzione delle callback.
asyncHook.enable()
// Disabilita l'ascolto di nuovi eventi asincroni.
asyncHook.disable()
//
// Di seguito sono riportate le callback che possono essere passate a createHook().
//
// init() viene chiamato durante la costruzione dell'oggetto. La risorsa potrebbe non aver
// completato la costruzione quando viene eseguita questa callback. Pertanto, tutti i campi del
// risorsa a cui fa riferimento "asyncId" potrebbero non essere stati popolati.
function init(asyncId, type, triggerAsyncId, resource) {}
// before() viene chiamato appena prima che venga chiamata la callback della risorsa. Può essere
// chiamato 0-N volte per gli handle (come TCPWrap) e verrà chiamato esattamente 1
// volta per le richieste (come FSReqCallback).
function before(asyncId) {}
// after() viene chiamato subito dopo che la callback della risorsa ha terminato.
function after(asyncId) {}
// destroy() viene chiamato quando la risorsa viene distrutta.
function destroy(asyncId) {}
// promiseResolve() viene chiamato solo per le risorse promise, quando la
// funzione resolve() passata al costruttore Promise viene invocata
// (direttamente o tramite altri mezzi per risolvere una promise).
function promiseResolve(asyncId) {}
async_hooks.createHook(callbacks)
Aggiunto in: v8.1.0
callbacks
<Oggetto> I Callback Hook da registrareinit
<Funzione> Ilcallback init
.before
<Funzione> Ilcallback before
.after
<Funzione> Ilcallback after
.destroy
<Funzione> Ilcallback destroy
.promiseResolve
<Funzione> Ilcallback promiseResolve
.
Restituisce: <AsyncHook> Istanza usata per disabilitare e abilitare gli hook
Registra le funzioni da chiamare per i diversi eventi del ciclo di vita di ciascuna operazione asincrona.
I callback init()
/before()
/after()
/destroy()
vengono chiamati per il rispettivo evento asincrono durante il ciclo di vita di una risorsa.
Tutti i callback sono opzionali. Ad esempio, se è necessario tracciare solo la pulizia delle risorse, è necessario passare solo il callback destroy
. Le specifiche di tutte le funzioni che possono essere passate a callbacks
sono nella sezione 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) {},
})
I callback verranno ereditati tramite la catena di prototipi:
class MyAsyncCallbacks {
init(asyncId, type, triggerAsyncId, resource) {}
destroy(asyncId) {}
}
class MyAddedCallbacks extends MyAsyncCallbacks {
before(asyncId) {}
after(asyncId) {}
}
const asyncHook = async_hooks.createHook(new MyAddedCallbacks())
Poiché le promise sono risorse asincrone il cui ciclo di vita viene tracciato tramite il meccanismo degli async hook, i callback init()
, before()
, after()
e destroy()
non devono essere funzioni async che restituiscono promise.
Gestione degli errori
Se una qualsiasi callback AsyncHook
genera un'eccezione, l'applicazione stamperà la traccia dello stack e terminerà. Il percorso di uscita segue quello di un'eccezione non gestita, ma tutti i listener di 'uncaughtException'
vengono rimossi, forzando così la terminazione del processo. Le callback 'exit'
verranno comunque chiamate a meno che l'applicazione non venga eseguita con --abort-on-uncaught-exception
, nel qual caso verrà stampata una traccia dello stack e l'applicazione terminerà, lasciando un file core.
Il motivo di questo comportamento di gestione degli errori è che queste callback vengono eseguite in punti potenzialmente volatili del ciclo di vita di un oggetto, ad esempio durante la costruzione e la distruzione della classe. Per questo motivo, si ritiene necessario interrompere rapidamente il processo al fine di evitare un'interruzione involontaria in futuro. Ciò è soggetto a modifiche in futuro se viene eseguita un'analisi completa per garantire che un'eccezione possa seguire il normale flusso di controllo senza effetti collaterali involontari.
Stampa nelle callback AsyncHook
Poiché la stampa sulla console è un'operazione asincrona, console.log()
causerà la chiamata delle callback AsyncHook
. L'utilizzo di console.log()
o operazioni asincrone simili all'interno di una funzione di callback AsyncHook
causerà una ricorsione infinita. Una soluzione semplice a questo problema durante il debug è utilizzare un'operazione di logging sincrona come fs.writeFileSync(file, msg, flag)
. Questo stamperà nel file e non invocherà AsyncHook
in modo ricorsivo perché è sincrono.
import { writeFileSync } from 'node:fs'
import { format } from 'node:util'
function debug(...args) {
// Utilizzare una funzione come questa durante il debug all'interno di una callback AsyncHook
writeFileSync('log.out', `${format(...args)}\n`, { flag: 'a' })
}
const fs = require('node:fs')
const util = require('node:util')
function debug(...args) {
// Utilizzare una funzione come questa durante il debug all'interno di una callback AsyncHook
fs.writeFileSync('log.out', `${util.format(...args)}\n`, { flag: 'a' })
}
Se è necessaria un'operazione asincrona per il logging, è possibile tenere traccia di cosa ha causato l'operazione asincrona utilizzando le informazioni fornite da AsyncHook
stesso. Il logging dovrebbe quindi essere saltato quando è stato il logging stesso a causare la chiamata della callback AsyncHook
. In questo modo, la ricorsione altrimenti infinita viene interrotta.
Classe: AsyncHook
La classe AsyncHook
espone un'interfaccia per tracciare gli eventi di durata delle operazioni asincrone.
asyncHook.enable()
- Restituisce: <AsyncHook> Un riferimento a
asyncHook
.
Abilita i callback per un'istanza AsyncHook
data. Se non vengono forniti callback, l'abilitazione è un'operazione no-op.
L'istanza AsyncHook
è disabilitata per impostazione predefinita. Se l'istanza AsyncHook
deve essere abilitata immediatamente dopo la creazione, è possibile utilizzare il seguente pattern.
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()
- Restituisce: <AsyncHook> Un riferimento a
asyncHook
.
Disabilita i callback per un'istanza AsyncHook
data dal pool globale di callback AsyncHook
da eseguire. Una volta disabilitato un hook, non verrà più chiamato fino a quando non sarà abilitato.
Per coerenza dell'API, disable()
restituisce anche l'istanza AsyncHook
.
Callback Hook
Gli eventi chiave nella durata degli eventi asincroni sono stati classificati in quattro aree: instanziazione, prima/dopo che viene chiamato il callback e quando l'istanza viene distrutta.
init(asyncId, type, triggerAsyncId, resource)
asyncId
<number> Un ID univoco per la risorsa asincrona.type
<string> Il tipo di risorsa asincrona.triggerAsyncId
<number> L'ID univoco della risorsa asincrona nel cui contesto di esecuzione è stata creata questa risorsa asincrona.resource
<Object> Riferimento alla risorsa che rappresenta l'operazione asincrona, deve essere rilasciato durante destroy.
Chiamato quando viene costruita una classe che ha la possibilità di emettere un evento asincrono. Questo non significa che l'istanza debba chiamare before
/after
prima che venga chiamato destroy
, ma solo che esiste la possibilità.
Questo comportamento può essere osservato facendo qualcosa come aprire una risorsa e poi chiuderla prima che la risorsa possa essere utilizzata. Il seguente snippet lo dimostra.
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))
A ogni nuova risorsa viene assegnato un ID univoco nell'ambito dell'istanza corrente di Node.js.
type
type
è una stringa che identifica il tipo di risorsa che ha causato la chiamata a init
. In generale, corrisponderà al nome del costruttore della risorsa.
Il type
delle risorse create da Node.js stesso può cambiare in qualsiasi release di Node.js. I valori validi includono TLSWRAP
, TCPWRAP
, TCPSERVERWRAP
, GETADDRINFOREQWRAP
, FSREQCALLBACK
, Microtask
e Timeout
. Controlla il codice sorgente della versione di Node.js utilizzata per ottenere la lista completa.
Inoltre, gli utenti di AsyncResource
creano risorse asincrone indipendentemente da Node.js stesso.
Esiste anche il tipo di risorsa PROMISE
, che viene utilizzato per tenere traccia delle istanze Promise
e del lavoro asincrono pianificato da esse.
Gli utenti possono definire il proprio type
quando utilizzano l'API embedder pubblica.
È possibile che si verifichino collisioni di nomi di tipo. Gli embedder sono incoraggiati a utilizzare prefissi univoci, come il nome del pacchetto npm, per prevenire collisioni quando si ascoltano gli hook.
triggerAsyncId
triggerAsyncId
è l'asyncId
della risorsa che ha causato (o "attivato") l'inizializzazione della nuova risorsa e che ha causato la chiamata a init
. Questo è diverso da async_hooks.executionAsyncId()
che mostra solo quando è stata creata una risorsa, mentre triggerAsyncId
mostra perché è stata creata una risorsa.
Di seguito è riportata una semplice dimostrazione di 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)
Output quando si colpisce il server con nc localhost 8080
:
TCPSERVERWRAP(5): trigger: 1 execution: 1
TCPWRAP(7): trigger: 5 execution: 0
TCPSERVERWRAP
è il server che riceve le connessioni.
TCPWRAP
è la nuova connessione dal client. Quando viene effettuata una nuova connessione, l'istanza TCPWrap
viene costruita immediatamente. Questo avviene al di fuori di qualsiasi stack JavaScript. (Un executionAsyncId()
di 0
significa che viene eseguito da C++ senza nessuno stack JavaScript sopra di esso.) Con solo queste informazioni, sarebbe impossibile collegare le risorse in termini di ciò che ha causato la loro creazione, quindi a triggerAsyncId
viene affidato il compito di propagare quale risorsa è responsabile dell'esistenza della nuova risorsa.
resource
resource
è un oggetto che rappresenta l'effettiva risorsa asincrona che è stata inizializzata. L'API per accedere all'oggetto può essere specificata dal creatore della risorsa. Le risorse create da Node.js stesso sono interne e possono cambiare in qualsiasi momento. Pertanto, non viene specificata alcuna API per queste.
In alcuni casi, l'oggetto risorsa viene riutilizzato per motivi di prestazioni, quindi non è sicuro usarlo come chiave in una WeakMap
o aggiungervi delle proprietà.
Esempio di contesto asincrono
Il caso d'uso del tracciamento del contesto è coperto dall'API stabile AsyncLocalStorage
. Questo esempio illustra solo il funzionamento degli hook asincroni, ma AsyncLocalStorage
si adatta meglio a questo caso d'uso.
Il seguente è un esempio con ulteriori informazioni sulle chiamate a init
tra le chiamate before
e after
, in particolare come apparirà la callback di listen()
. La formattazione dell'output è leggermente più elaborata per rendere più facile la visualizzazione del contesto di chiamata.
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)
})
Output dall'avvio del solo server:
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
Come illustrato nell'esempio, executionAsyncId()
e execution
specificano ciascuno il valore del contesto di esecuzione corrente; che è delineato dalle chiamate a before
e after
.
Usando solo execution
per graficare l'allocazione delle risorse si ottiene il seguente risultato:
root(1)
^
|
TickObject(6)
^
|
Timeout(7)
TCPSERVERWRAP
non fa parte di questo grafico, anche se è stata la ragione per cui è stato chiamato console.log()
. Questo perché il binding a una porta senza nome host è un'operazione sincrona, ma per mantenere un'API completamente asincrona, la callback dell'utente viene inserita in un process.nextTick()
. Questo è il motivo per cui TickObject
è presente nell'output ed è un "genitore" per la callback di .listen()
.
Il grafico mostra solo quando è stata creata una risorsa, non perché, quindi per tracciare il perché usa triggerAsyncId
. Che può essere rappresentato con il seguente grafico:
bootstrap(1)
|
˅
TCPSERVERWRAP(5)
|
˅
TickObject(6)
|
˅
Timeout(7)
before(asyncId)
asyncId
<number>
Quando un'operazione asincrona viene avviata (come un server TCP che riceve una nuova connessione) o completata (come la scrittura di dati su disco), viene chiamata una callback per notificare l'utente. La callback before
viene chiamata subito prima che venga eseguita la suddetta callback. asyncId
è l'identificatore univoco assegnato alla risorsa che sta per eseguire la callback.
La callback before
verrà chiamata da 0 a N volte. La callback before
verrà in genere chiamata 0 volte se l'operazione asincrona è stata annullata o, ad esempio, se un server TCP non riceve connessioni. Le risorse asincrone persistenti come un server TCP chiameranno in genere la callback before
più volte, mentre altre operazioni come fs.open()
la chiameranno una sola volta.
after(asyncId)
asyncId
<number>
Chiamata immediatamente dopo che la callback specificata in before
è stata completata.
Se si verifica un'eccezione non gestita durante l'esecuzione della callback, allora after
verrà eseguita dopo che l'evento 'uncaughtException'
è stato emesso o che il gestore di un domain
è stato eseguito.
destroy(asyncId)
asyncId
<number>
Chiamata dopo che la risorsa corrispondente a asyncId
viene distrutta. Viene anche chiamata in modo asincrono dall'API embedder emitDestroy()
.
Alcune risorse dipendono dalla garbage collection per la pulizia, quindi se viene fatto un riferimento all'oggetto resource
passato a init
è possibile che destroy
non venga mai chiamata, causando una perdita di memoria nell'applicazione. Se la risorsa non dipende dalla garbage collection, questo non sarà un problema.
L'utilizzo dell'hook destroy comporta un sovraccarico aggiuntivo perché abilita il tracciamento delle istanze Promise
tramite il garbage collector.
promiseResolve(asyncId)
Aggiunto in: v8.6.0
asyncId
<number>
Chiamata quando la funzione resolve
passata al costruttore Promise
viene invocata (direttamente o attraverso altri mezzi per risolvere una promise).
resolve()
non esegue alcun lavoro sincrono osservabile.
La Promise
non è necessariamente completata o rifiutata a questo punto se la Promise
è stata risolta assumendo lo stato di un'altra Promise
.
new Promise(resolve => resolve(true)).then(a => {})
chiama le seguenti callback:
init per PROMISE con id 5, id trigger: 1
promise resolve 5 # corrisponde a resolve(true)
init per PROMISE con id 6, id trigger: 5 # la Promise restituita da then()
before 6 # viene eseguita la callback then()
promise resolve 6 # la callback then() risolve la promise restituendo
after 6
async_hooks.executionAsyncResource()
Aggiunto in: v13.9.0, v12.17.0
- Restituisce: <Object> La risorsa che rappresenta l'esecuzione corrente. Utile per memorizzare dati all'interno della risorsa.
Gli oggetti risorsa restituiti da executionAsyncResource()
sono il più delle volte oggetti handle interni di Node.js con API non documentate. L'uso di qualsiasi funzione o proprietà sull'oggetto è probabile che provochi il crash dell'applicazione e deve essere evitato.
L'uso di executionAsyncResource()
nel contesto di esecuzione di primo livello restituirà un oggetto vuoto poiché non ci sono handle o oggetti richiesta da usare, ma avere un oggetto che rappresenta il livello superiore può essere utile.
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
})
Questo può essere usato per implementare lo storage locale di continuazione senza l'uso di una Map
di tracciamento per memorizzare i metadati:
import { createServer } from 'node:http'
import { executionAsyncId, executionAsyncResource, createHook } from 'node:async_hooks'
const sym = Symbol('state') // Simbolo privato per evitare l'inquinamento
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') // Simbolo privato per evitare l'inquinamento
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()
[Cronologia]
Versione | Modifiche |
---|---|
v8.2.0 | Rinominata da currentId . |
v8.1.0 | Aggiunta in: v8.1.0 |
- Restituisce: <numero> L'
asyncId
del contesto di esecuzione corrente. Utile per tracciare quando qualcosa chiama.
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()
})
L'ID restituito da executionAsyncId()
è correlato al tempismo dell'esecuzione, non alla causalità (che è coperta da triggerAsyncId()
):
const server = net
.createServer(conn => {
// Restituisce l'ID del server, non della nuova connessione, perché la
// callback viene eseguita nell'ambito di esecuzione del MakeCallback() del server.
async_hooks.executionAsyncId()
})
.listen(port, () => {
// Restituisce l'ID di un TickObject (process.nextTick()) perché tutte le
// callback passate a .listen() sono avvolte in un nextTick().
async_hooks.executionAsyncId()
})
I contesti delle Promise potrebbero non ottenere executionAsyncIds
precisi per impostazione predefinita. Vedi la sezione sul tracciamento dell'esecuzione delle promise.
async_hooks.triggerAsyncId()
- Restituisce: <numero> L'ID della risorsa responsabile della chiamata alla callback che è attualmente in esecuzione.
const server = net
.createServer(conn => {
// La risorsa che ha causato (o attivato) la chiamata di questa callback
// era quella della nuova connessione. Quindi il valore restituito di triggerAsyncId()
// è l'asyncId di "conn".
async_hooks.triggerAsyncId()
})
.listen(port, () => {
// Anche se tutte le callback passate a .listen() sono avvolte in un nextTick()
// la callback stessa esiste perché è stata fatta la chiamata a .listen() del server.
// Quindi il valore restituito sarebbe l'ID del server.
async_hooks.triggerAsyncId()
})
I contesti delle Promise potrebbero non ottenere triggerAsyncId
validi per impostazione predefinita. Vedi la sezione sul tracciamento dell'esecuzione delle promise.
async_hooks.asyncWrapProviders
Aggiunto in: v17.2.0, v16.14.0
- Restituisce: una mappa dei tipi di provider all'ID numerico corrispondente. Questa mappa contiene tutti i tipi di eventi che potrebbero essere emessi dall'evento
async_hooks.init()
.
Questa funzionalità sopprime l'uso deprecato di process.binding('async_wrap').Providers
. Vedi: DEP0111
Tracciamento dell'esecuzione delle Promise
Per impostazione predefinita, alle esecuzioni delle promise non vengono assegnati asyncId
a causa della natura relativamente costosa dell'API di introspezione delle promise fornita da V8. Ciò significa che i programmi che utilizzano le promise o async
/await
non otterranno ID di esecuzione e trigger corretti per i contesti di callback delle promise per impostazione predefinita.
import { executionAsyncId, triggerAsyncId } from 'node:async_hooks'
Promise.resolve(1729).then(() => {
console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`)
})
// produce:
// eid 1 tid 0
const { executionAsyncId, triggerAsyncId } = require('node:async_hooks')
Promise.resolve(1729).then(() => {
console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`)
})
// produce:
// eid 1 tid 0
Si osservi che il callback then()
dichiara di essere stato eseguito nel contesto dello scope esterno, anche se è stato coinvolto un salto asincrono. Inoltre, il valore di triggerAsyncId
è 0
, il che significa che ci manca il contesto sulla risorsa che ha causato (attivato) l'esecuzione del callback then()
.
L'installazione di hook asincroni tramite async_hooks.createHook
abilita il tracciamento dell'esecuzione delle promise:
import { createHook, executionAsyncId, triggerAsyncId } from 'node:async_hooks'
createHook({ init() {} }).enable() // forza l'abilitazione di PromiseHooks.
Promise.resolve(1729).then(() => {
console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`)
})
// produce:
// eid 7 tid 6
const { createHook, executionAsyncId, triggerAsyncId } = require('node:async_hooks')
createHook({ init() {} }).enable() // forza l'abilitazione di PromiseHooks.
Promise.resolve(1729).then(() => {
console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`)
})
// produce:
// eid 7 tid 6
In questo esempio, l'aggiunta di qualsiasi funzione hook reale ha abilitato il tracciamento delle promise. Ci sono due promise nell'esempio sopra; la promise creata da Promise.resolve()
e la promise restituita dalla chiamata a then()
. Nell'esempio sopra, la prima promise ha ottenuto l'asyncId
6
e la seconda ha ottenuto asyncId
7
. Durante l'esecuzione del callback then()
, stiamo eseguendo nel contesto della promise con asyncId
7
. Questa promise è stata attivata dalla risorsa asincrona 6
.
Un'altra sottigliezza con le promise è che i callback before
e after
vengono eseguiti solo sulle promise concatenate. Ciò significa che le promise non create da then()
/catch()
non avranno i callback before
e after
attivati su di esse. Per maggiori dettagli, consulta i dettagli dell'API V8 PromiseHooks.
API di incorporamento JavaScript
Gli sviluppatori di librerie che gestiscono le proprie risorse asincrone eseguendo attività come I/O, pool di connessioni o gestione di code di callback possono utilizzare l'API JavaScript AsyncResource
in modo che vengano chiamati tutti i callback appropriati.
Classe: AsyncResource
La documentazione per questa classe è stata spostata in AsyncResource
.
Classe: AsyncLocalStorage
La documentazione per questa classe è stata spostata in AsyncLocalStorage
.