Suivi de contexte asynchrone
[Stable : 2 - Stable]
Stable : 2 Stability : 2 - Stable
Code source : lib/async_hooks.js
Introduction
Ces classes servent à associer un état et à le propager à travers les callbacks et les chaînes de promesses. Elles permettent de stocker des données pendant toute la durée d'une requête web ou de toute autre durée asynchrone. Cela est similaire au stockage local par thread dans d'autres langages.
Les classes AsyncLocalStorage
et AsyncResource
font partie du module node:async_hooks
:
import { AsyncLocalStorage, AsyncResource } from 'node:async_hooks'
const { AsyncLocalStorage, AsyncResource } = require('node:async_hooks')
Classe : AsyncLocalStorage
[Historique]
Version | Modifications |
---|---|
v16.4.0 | AsyncLocalStorage est maintenant Stable. Précédemment, il était Expérimental. |
v13.10.0, v12.17.0 | Ajouté dans : v13.10.0, v12.17.0 |
Cette classe crée des magasins qui restent cohérents tout au long des opérations asynchrones.
Bien que vous puissiez créer votre propre implémentation par-dessus le module node:async_hooks
, AsyncLocalStorage
est à privilégier car il s'agit d'une implémentation performante et sécurisée en mémoire qui implique des optimisations significatives et non évidentes à implémenter.
L'exemple suivant utilise AsyncLocalStorage
pour construire un journal simple qui attribue des ID aux requêtes HTTP entrantes et les inclut dans les messages enregistrés dans chaque requête.
import http from 'node:http'
import { AsyncLocalStorage } from 'node:async_hooks'
const asyncLocalStorage = new AsyncLocalStorage()
function logWithId(msg) {
const id = asyncLocalStorage.getStore()
console.log(`${id !== undefined ? id : '-'}:`, msg)
}
let idSeq = 0
http
.createServer((req, res) => {
asyncLocalStorage.run(idSeq++, () => {
logWithId('start')
// Imaginez n'importe quelle chaîne d'opérations asynchrones ici
setImmediate(() => {
logWithId('finish')
res.end()
})
})
})
.listen(8080)
http.get('http://localhost:8080')
http.get('http://localhost:8080')
// Affiche :
// 0: start
// 1: start
// 0: finish
// 1: finish
const http = require('node:http')
const { AsyncLocalStorage } = require('node:async_hooks')
const asyncLocalStorage = new AsyncLocalStorage()
function logWithId(msg) {
const id = asyncLocalStorage.getStore()
console.log(`${id !== undefined ? id : '-'}:`, msg)
}
let idSeq = 0
http
.createServer((req, res) => {
asyncLocalStorage.run(idSeq++, () => {
logWithId('start')
// Imaginez n'importe quelle chaîne d'opérations asynchrones ici
setImmediate(() => {
logWithId('finish')
res.end()
})
})
})
.listen(8080)
http.get('http://localhost:8080')
http.get('http://localhost:8080')
// Affiche :
// 0: start
// 1: start
// 0: finish
// 1: finish
Chaque instance de AsyncLocalStorage
maintient un contexte de stockage indépendant. Plusieurs instances peuvent exister simultanément en toute sécurité sans risque d'interférence entre leurs données.
new AsyncLocalStorage()
[Historique]
Version | Modifications |
---|---|
v19.7.0, v18.16.0 | Option onPropagate expérimentale supprimée. |
v19.2.0, v18.13.0 | Ajout de l'option onPropagate . |
v13.10.0, v12.17.0 | Ajouté dans : v13.10.0, v12.17.0 |
Crée une nouvelle instance de AsyncLocalStorage
. Le stockage n'est fourni que dans un appel run()
ou après un appel enterWith()
.
Méthode statique : AsyncLocalStorage.bind(fn)
Ajouté dans : v19.8.0, v18.16.0
[Stable : 1 - Expérimental]
Stable : 1 Stabilité : 1 - Expérimental
fn
<Function> La fonction à lier au contexte d'exécution actuel.- Retourne : <Function> Une nouvelle fonction qui appelle
fn
dans le contexte d'exécution capturé.
Lie la fonction donnée au contexte d'exécution actuel.
Méthode statique : AsyncLocalStorage.snapshot()
Ajouté dans : v19.8.0, v18.16.0
[Stable : 1 - Expérimental]
Stable : 1 Stabilité : 1 - Expérimental
- Retourne : <Function> Une nouvelle fonction avec la signature
(fn: (...args) : R, ...args) : R
.
Capture le contexte d'exécution actuel et retourne une fonction qui accepte une fonction en argument. Chaque fois que la fonction retournée est appelée, elle appelle la fonction qui lui est passée dans le contexte capturé.
const asyncLocalStorage = new AsyncLocalStorage()
const runInAsyncScope = asyncLocalStorage.run(123, () => AsyncLocalStorage.snapshot())
const result = asyncLocalStorage.run(321, () => runInAsyncScope(() => asyncLocalStorage.getStore()))
console.log(result) // retourne 123
AsyncLocalStorage.snapshot()
peut remplacer l'utilisation d'AsyncResource pour des objectifs simples de suivi du contexte asynchrone, par exemple :
class Foo {
#runInAsyncScope = AsyncLocalStorage.snapshot()
get() {
return this.#runInAsyncScope(() => asyncLocalStorage.getStore())
}
}
const foo = asyncLocalStorage.run(123, () => new Foo())
console.log(asyncLocalStorage.run(321, () => foo.get())) // retourne 123
asyncLocalStorage.disable()
Ajouté dans : v13.10.0, v12.17.0
[Stable : 1 - Expérimental]
Stable : 1 Stabilité : 1 - Expérimental
Désactive l'instance de AsyncLocalStorage
. Tous les appels suivants à asyncLocalStorage.getStore()
retourneront undefined
jusqu'à ce que asyncLocalStorage.run()
ou asyncLocalStorage.enterWith()
soit appelé à nouveau.
Lorsque vous appelez asyncLocalStorage.disable()
, tous les contextes actuels liés à l'instance seront quittés.
L'appel à asyncLocalStorage.disable()
est requis avant que asyncLocalStorage
puisse être ramassé par le garbage collector. Cela ne s'applique pas aux magasins fournis par asyncLocalStorage
, car ces objets sont ramassés par le garbage collector en même temps que les ressources asynchrones correspondantes.
Utilisez cette méthode lorsque asyncLocalStorage
n'est plus utilisé dans le processus actuel.
asyncLocalStorage.getStore()
Ajouté dans : v13.10.0, v12.17.0
- Retourne : <any>
Retourne le magasin actuel. S'il est appelé en dehors d'un contexte asynchrone initialisé en appelant asyncLocalStorage.run()
ou asyncLocalStorage.enterWith()
, il retourne undefined
.
asyncLocalStorage.enterWith(store)
Ajouté dans : v13.11.0, v12.17.0
[Stable : 1 - Expérimental]
Stable : 1 Stabilité : 1 - Expérimental
store
<any>
Passe au contexte pour le reste de l'exécution synchrone actuelle, puis persiste le magasin via tous les appels asynchrones suivants.
Exemple :
const store = { id: 1 }
// Remplace le magasin précédent par l'objet de magasin donné
asyncLocalStorage.enterWith(store)
asyncLocalStorage.getStore() // Retourne l'objet store
someAsyncOperation(() => {
asyncLocalStorage.getStore() // Retourne le même objet
})
Cette transition se poursuivra pour l'intégralité de l'exécution synchrone. Cela signifie que si, par exemple, le contexte est entré dans un gestionnaire d'événements, les gestionnaires d'événements suivants s'exécuteront également dans ce contexte, sauf s'ils sont spécifiquement liés à un autre contexte avec une AsyncResource
. C'est pourquoi run()
devrait être préféré à enterWith()
à moins qu'il n'y ait de fortes raisons d'utiliser cette dernière méthode.
const store = { id: 1 }
emitter.on('my-event', () => {
asyncLocalStorage.enterWith(store)
})
emitter.on('my-event', () => {
asyncLocalStorage.getStore() // Retourne le même objet
})
asyncLocalStorage.getStore() // Retourne undefined
emitter.emit('my-event')
asyncLocalStorage.getStore() // Retourne le même objet
asyncLocalStorage.run(store, callback[, ...args])
Ajouté dans : v13.10.0, v12.17.0
store
<any>callback
<Function>...args
<any>
Exécute une fonction de manière synchrone au sein d'un contexte et renvoie sa valeur de retour. Le magasin n'est pas accessible en dehors de la fonction de rappel. Le magasin est accessible à toutes les opérations asynchrones créées au sein de la fonction de rappel.
Les arguments optionnels args
sont passés à la fonction de rappel.
Si la fonction de rappel lève une erreur, l'erreur est également levée par run()
. La trace de la pile n'est pas affectée par cet appel et le contexte est quitté.
Exemple :
const store = { id: 2 }
try {
asyncLocalStorage.run(store, () => {
asyncLocalStorage.getStore() // Renvoie l'objet store
setTimeout(() => {
asyncLocalStorage.getStore() // Renvoie l'objet store
}, 200)
throw new Error()
})
} catch (e) {
asyncLocalStorage.getStore() // Renvoie undefined
// L'erreur sera interceptée ici
}
asyncLocalStorage.exit(callback[, ...args])
Ajouté dans : v13.10.0, v12.17.0
[Stable : 1 - Expérimental]
Stable : 1 Stabilité : 1 - Expérimental
callback
<Function>...args
<any>
Exécute une fonction de manière synchrone en dehors d'un contexte et renvoie sa valeur de retour. Le magasin n'est pas accessible au sein de la fonction de rappel ou des opérations asynchrones créées au sein de la fonction de rappel. Tout appel à getStore()
effectué au sein de la fonction de rappel renverra toujours undefined
.
Les arguments optionnels args
sont passés à la fonction de rappel.
Si la fonction de rappel lève une erreur, l'erreur est également levée par exit()
. La trace de la pile n'est pas affectée par cet appel et le contexte est réintégré.
Exemple :
// Au sein d'un appel à run
try {
asyncLocalStorage.getStore() // Renvoie l'objet ou la valeur du store
asyncLocalStorage.exit(() => {
asyncLocalStorage.getStore() // Renvoie undefined
throw new Error()
})
} catch (e) {
asyncLocalStorage.getStore() // Renvoie le même objet ou la même valeur
// L'erreur sera interceptée ici
}
Utilisation avec async/await
Si, au sein d'une fonction asynchrone, un seul appel await
doit s'exécuter dans un contexte, le modèle suivant doit être utilisé :
async function fn() {
await asyncLocalStorage.run(new Map(), () => {
asyncLocalStorage.getStore().set('key', value)
return foo() // La valeur de retour de foo sera attendue
})
}
Dans cet exemple, le stockage n'est disponible que dans la fonction de rappel et les fonctions appelées par foo
. En dehors de run
, l'appel de getStore
renverra undefined
.
Dépannage : perte de contexte
Dans la plupart des cas, AsyncLocalStorage
fonctionne sans problème. Dans de rares situations, le stockage actuel est perdu dans l'une des opérations asynchrones.
Si votre code est basé sur des rappels, il suffit de le promettre avec util.promisify()
pour qu'il commence à fonctionner avec les promesses natives.
Si vous devez utiliser une API basée sur des rappels ou si votre code suppose une implémentation thenable personnalisée, utilisez la classe AsyncResource
pour associer l'opération asynchrone au contexte d'exécution correct. Trouvez l'appel de fonction responsable de la perte de contexte en consignant le contenu de asyncLocalStorage.getStore()
après les appels que vous suspectez d'être responsables de la perte. Lorsque le code consigne undefined
, le dernier rappel appelé est probablement responsable de la perte de contexte.
Classe : AsyncResource
[Historique]
Version | Modifications |
---|---|
v16.4.0 | AsyncResource est désormais stable. Auparavant, il était expérimental. |
La classe AsyncResource
est conçue pour être étendue par les ressources asynchrones de l'intégrateur. Grâce à cela, les utilisateurs peuvent facilement déclencher les événements de durée de vie de leurs propres ressources.
Le crochet init
sera déclenché lorsqu'un AsyncResource
sera instancié.
Voici un aperçu de l'API AsyncResource
.
import { AsyncResource, executionAsyncId } from 'node:async_hooks'
// AsyncResource() est destiné à être étendu. L'instanciation d'un
// nouveau AsyncResource() déclenche également init. Si triggerAsyncId est omis, alors
// async_hook.executionAsyncId() est utilisé.
const asyncResource = new AsyncResource(type, { triggerAsyncId: executionAsyncId(), requireManualDestroy: false })
// Exécute une fonction dans le contexte d'exécution de la ressource. Cela va
// * établir le contexte de la ressource
// * déclencher les rappels AsyncHooks avant
// * appeler la fonction `fn` fournie avec les arguments fournis
// * déclencher les rappels AsyncHooks après
// * restaurer le contexte d'exécution original
asyncResource.runInAsyncScope(fn, thisArg, ...args)
// Appeler les rappels de destruction AsyncHooks.
asyncResource.emitDestroy()
// Renvoie l'ID unique attribué à l'instance AsyncResource.
asyncResource.asyncId()
// Renvoie l'ID de déclenchement pour l'instance AsyncResource.
asyncResource.triggerAsyncId()
const { AsyncResource, executionAsyncId } = require('node:async_hooks')
// AsyncResource() est destiné à être étendu. L'instanciation d'un
// nouveau AsyncResource() déclenche également init. Si triggerAsyncId est omis, alors
// async_hook.executionAsyncId() est utilisé.
const asyncResource = new AsyncResource(type, { triggerAsyncId: executionAsyncId(), requireManualDestroy: false })
// Exécute une fonction dans le contexte d'exécution de la ressource. Cela va
// * établir le contexte de la ressource
// * déclencher les rappels AsyncHooks avant
// * appeler la fonction `fn` fournie avec les arguments fournis
// * déclencher les rappels AsyncHooks après
// * restaurer le contexte d'exécution original
asyncResource.runInAsyncScope(fn, thisArg, ...args)
// Appeler les rappels de destruction AsyncHooks.
asyncResource.emitDestroy()
// Renvoie l'ID unique attribué à l'instance AsyncResource.
asyncResource.asyncId()
// Renvoie l'ID de déclenchement pour l'instance AsyncResource.
asyncResource.triggerAsyncId()
new AsyncResource(type[, options])
type
<string> Le type d'événement asynchrone.options
<Object>triggerAsyncId
<number> L'ID du contexte d'exécution qui a créé cet événement asynchrone. Défaut :executionAsyncId()
.requireManualDestroy
<boolean> Si défini surtrue
, désactiveemitDestroy
lorsque l'objet est collecté par le garbage collector. Cela n'a généralement pas besoin d'être défini (même siemitDestroy
est appelé manuellement), sauf si l'asyncId
de la ressource est récupéré et queemitDestroy
de l'API sensible est appelé avec celui-ci. Lorsqu'il est défini surfalse
, l'appelemitDestroy
lors de la collecte des ordures n'aura lieu que s'il existe au moins un hookdestroy
actif. Défaut :false
.
Exemple d'utilisation :
class DBQuery extends AsyncResource {
constructor(db) {
super('DBQuery')
this.db = db
}
getInfo(query, callback) {
this.db.get(query, (err, data) => {
this.runInAsyncScope(callback, null, err, data)
})
}
close() {
this.db = null
this.emitDestroy()
}
}
Méthode statique : AsyncResource.bind(fn[, type[, thisArg]])
[Historique]
Version | Modifications |
---|---|
v20.0.0 | La propriété asyncResource ajoutée à la fonction liée a été dépréciée et sera supprimée dans une future version. |
v17.8.0, v16.15.0 | Modification de la valeur par défaut lorsque thisArg est indéfini pour utiliser this de l'appelant. |
v16.0.0 | Ajout de thisArg optionnel. |
v14.8.0, v12.19.0 | Ajouté dans : v14.8.0, v12.19.0 |
fn
<Function> La fonction à lier au contexte d'exécution actuel.type
<string> Un nom optionnel à associer à laAsyncResource
sous-jacente.thisArg
<any>
Lie la fonction donnée au contexte d'exécution actuel.
asyncResource.bind(fn[, thisArg])
[Historique]
Version | Modifications |
---|---|
v20.0.0 | La propriété asyncResource ajoutée à la fonction liée est obsolète et sera supprimée dans une future version. |
v17.8.0, v16.15.0 | Modification de la valeur par défaut lorsque thisArg est indéfini pour utiliser this de l'appelant. |
v16.0.0 | Ajout de thisArg en option. |
v14.8.0, v12.19.0 | Ajouté dans : v14.8.0, v12.19.0 |
fn
<Fonction> La fonction à lier à la portéeAsyncResource
actuelle.thisArg
<any>
Lie la fonction donnée à exécuter à la portée de ce AsyncResource
.
asyncResource.runInAsyncScope(fn[, thisArg, ...args])
Ajouté dans : v9.6.0
fn
<Fonction> La fonction à appeler dans le contexte d'exécution de cette ressource asynchrone.thisArg
<any> Le récepteur à utiliser pour l'appel de fonction....args
<any> Arguments optionnels à passer à la fonction.
Appelez la fonction fournie avec les arguments fournis dans le contexte d'exécution de la ressource asynchrone. Cela établira le contexte, déclenchera les AsyncHooks avant les rappels, appellera la fonction, déclenchera les AsyncHooks après les rappels, puis restaurera le contexte d'exécution original.
asyncResource.emitDestroy()
- Retourne : <AsyncResource> Une référence à
asyncResource
.
Appelez tous les hooks destroy
. Cela ne devrait être appelé qu'une seule fois. Une erreur sera levée si elle est appelée plus d'une fois. Ceci doit être appelé manuellement. Si la ressource est laissée à la collecte par le GC, les hooks destroy
ne seront jamais appelés.
asyncResource.asyncId()
- Retour : <number> L’
asyncId
unique attribué à la ressource.
asyncResource.triggerAsyncId()
- Retour : <number> Le même
triggerAsyncId
qui est passé au constructeurAsyncResource
.
Utilisation de AsyncResource
pour un pool de threads Worker
L’exemple suivant montre comment utiliser la classe AsyncResource
pour fournir correctement le suivi asynchrone d’un pool de Worker
. D’autres pools de ressources, tels que les pools de connexions de base de données, peuvent suivre un modèle similaire.
En supposant que la tâche consiste à additionner deux nombres, en utilisant un fichier nommé task_processor.js
avec le contenu suivant :
import { parentPort } from 'node:worker_threads'
parentPort.on('message', task => {
parentPort.postMessage(task.a + task.b)
})
const { parentPort } = require('node:worker_threads')
parentPort.on('message', task => {
parentPort.postMessage(task.a + task.b)
})
un pool de Worker autour de celui-ci pourrait utiliser la structure suivante :
import { AsyncResource } from 'node:async_hooks'
import { EventEmitter } from 'node:events'
import { Worker } from 'node:worker_threads'
const kTaskInfo = Symbol('kTaskInfo')
const kWorkerFreedEvent = Symbol('kWorkerFreedEvent')
class WorkerPoolTaskInfo extends AsyncResource {
constructor(callback) {
super('WorkerPoolTaskInfo')
this.callback = callback
}
done(err, result) {
this.runInAsyncScope(this.callback, null, err, result)
this.emitDestroy() // `TaskInfo`s are used only once.
}
}
export default class WorkerPool extends EventEmitter {
constructor(numThreads) {
super()
this.numThreads = numThreads
this.workers = []
this.freeWorkers = []
this.tasks = []
for (let i = 0; i < numThreads; i++) this.addNewWorker()
// Any time the kWorkerFreedEvent is emitted, dispatch
// the next task pending in the queue, if any.
this.on(kWorkerFreedEvent, () => {
if (this.tasks.length > 0) {
const { task, callback } = this.tasks.shift()
this.runTask(task, callback)
}
})
}
addNewWorker() {
const worker = new Worker(new URL('task_processor.js', import.meta.url))
worker.on('message', result => {
// In case of success: Call the callback that was passed to `runTask`,
// remove the `TaskInfo` associated with the Worker, and mark it as free
// again.
worker[kTaskInfo].done(null, result)
worker[kTaskInfo] = null
this.freeWorkers.push(worker)
this.emit(kWorkerFreedEvent)
})
worker.on('error', err => {
// In case of an uncaught exception: Call the callback that was passed to
// `runTask` with the error.
if (worker[kTaskInfo]) worker[kTaskInfo].done(err, null)
else this.emit('error', err)
// Remove the worker from the list and start a new Worker to replace the
// current one.
this.workers.splice(this.workers.indexOf(worker), 1)
this.addNewWorker()
})
this.workers.push(worker)
this.freeWorkers.push(worker)
this.emit(kWorkerFreedEvent)
}
runTask(task, callback) {
if (this.freeWorkers.length === 0) {
// No free threads, wait until a worker thread becomes free.
this.tasks.push({ task, callback })
return
}
const worker = this.freeWorkers.pop()
worker[kTaskInfo] = new WorkerPoolTaskInfo(callback)
worker.postMessage(task)
}
close() {
for (const worker of this.workers) worker.terminate()
}
}
const { AsyncResource } = require('node:async_hooks')
const { EventEmitter } = require('node:events')
const path = require('node:path')
const { Worker } = require('node:worker_threads')
const kTaskInfo = Symbol('kTaskInfo')
const kWorkerFreedEvent = Symbol('kWorkerFreedEvent')
class WorkerPoolTaskInfo extends AsyncResource {
constructor(callback) {
super('WorkerPoolTaskInfo')
this.callback = callback
}
done(err, result) {
this.runInAsyncScope(this.callback, null, err, result)
this.emitDestroy() // `TaskInfo`s are used only once.
}
}
class WorkerPool extends EventEmitter {
constructor(numThreads) {
super()
this.numThreads = numThreads
this.workers = []
this.freeWorkers = []
this.tasks = []
for (let i = 0; i < numThreads; i++) this.addNewWorker()
// Any time the kWorkerFreedEvent is emitted, dispatch
// the next task pending in the queue, if any.
this.on(kWorkerFreedEvent, () => {
if (this.tasks.length > 0) {
const { task, callback } = this.tasks.shift()
this.runTask(task, callback)
}
})
}
addNewWorker() {
const worker = new Worker(path.resolve(__dirname, 'task_processor.js'))
worker.on('message', result => {
// In case of success: Call the callback that was passed to `runTask`,
// remove the `TaskInfo` associated with the Worker, and mark it as free
// again.
worker[kTaskInfo].done(null, result)
worker[kTaskInfo] = null
this.freeWorkers.push(worker)
this.emit(kWorkerFreedEvent)
})
worker.on('error', err => {
// In case of an uncaught exception: Call the callback that was passed to
// `runTask` with the error.
if (worker[kTaskInfo]) worker[kTaskInfo].done(err, null)
else this.emit('error', err)
// Remove the worker from the list and start a new Worker to replace the
// current one.
this.workers.splice(this.workers.indexOf(worker), 1)
this.addNewWorker()
})
this.workers.push(worker)
this.freeWorkers.push(worker)
this.emit(kWorkerFreedEvent)
}
runTask(task, callback) {
if (this.freeWorkers.length === 0) {
// No free threads, wait until a worker thread becomes free.
this.tasks.push({ task, callback })
return
}
const worker = this.freeWorkers.pop()
worker[kTaskInfo] = new WorkerPoolTaskInfo(callback)
worker.postMessage(task)
}
close() {
for (const worker of this.workers) worker.terminate()
}
}
module.exports = WorkerPool
Sans le suivi explicite ajouté par les objets WorkerPoolTaskInfo
, il semblerait que les rappels soient associés aux objets Worker
individuels. Cependant, la création des Worker
n’est pas associée à la création des tâches et ne fournit pas d’informations sur le moment où les tâches ont été planifiées.
Ce pool pourrait être utilisé comme suit :
import WorkerPool from './worker_pool.js'
import os from 'node:os'
const pool = new WorkerPool(os.availableParallelism())
let finished = 0
for (let i = 0; i < 10; i++) {
pool.runTask({ a: 42, b: 100 }, (err, result) => {
console.log(i, err, result)
if (++finished === 10) pool.close()
})
}
const WorkerPool = require('./worker_pool.js')
const os = require('node:os')
const pool = new WorkerPool(os.availableParallelism())
let finished = 0
for (let i = 0; i < 10; i++) {
pool.runTask({ a: 42, b: 100 }, (err, result) => {
console.log(i, err, result)
if (++finished === 10) pool.close()
})
}
Intégration d' AsyncResource
avec EventEmitter
Les écouteurs d'événements déclenchés par un EventEmitter
peuvent être exécutés dans un contexte d'exécution différent de celui qui était actif lorsque eventEmitter.on()
a été appelé.
L'exemple suivant montre comment utiliser la classe AsyncResource
pour associer correctement un écouteur d'événements au contexte d'exécution correct. La même approche peut être appliquée à un Stream
ou à une classe pilotée par événements similaire.
import { createServer } from 'node:http'
import { AsyncResource, executionAsyncId } from 'node:async_hooks'
const server = createServer((req, res) => {
req.on(
'close',
AsyncResource.bind(() => {
// Le contexte d'exécution est lié à la portée externe actuelle.
})
)
req.on('close', () => {
// Le contexte d'exécution est lié à la portée qui a provoqué l'émission de 'close'.
})
res.end()
}).listen(3000)
const { createServer } = require('node:http')
const { AsyncResource, executionAsyncId } = require('node:async_hooks')
const server = createServer((req, res) => {
req.on(
'close',
AsyncResource.bind(() => {
// Le contexte d'exécution est lié à la portée externe actuelle.
})
)
req.on('close', () => {
// Le contexte d'exécution est lié à la portée qui a provoqué l'émission de 'close'.
})
res.end()
}).listen(3000)