Seguimiento de contexto asíncrono
[Estable: 2 - Estable]
Estable: 2 Estabilidad: 2 - Estable
Código fuente: lib/async_hooks.js
Introducción
Estas clases se utilizan para asociar el estado y propagarlo a través de las devoluciones de llamada y las cadenas de promesas. Permiten almacenar datos durante la vida útil de una solicitud web o cualquier otra duración asíncrona. Es similar al almacenamiento local de subprocesos en otros lenguajes.
Las clases AsyncLocalStorage
y AsyncResource
forman parte del módulo node:async_hooks
:
import { AsyncLocalStorage, AsyncResource } from 'node:async_hooks'
const { AsyncLocalStorage, AsyncResource } = require('node:async_hooks')
Clase: AsyncLocalStorage
[Historial]
Versión | Cambios |
---|---|
v16.4.0 | AsyncLocalStorage ahora es Estable. Anteriormente, había sido Experimental. |
v13.10.0, v12.17.0 | Añadido en: v13.10.0, v12.17.0 |
Esta clase crea almacenes que permanecen coherentes a través de las operaciones asíncronas.
Si bien puede crear su propia implementación sobre el módulo node:async_hooks
, se debe preferir AsyncLocalStorage
ya que es una implementación eficiente y segura en memoria que implica optimizaciones significativas que no son obvias de implementar.
El siguiente ejemplo utiliza AsyncLocalStorage
para construir un registrador simple que asigna ID a las solicitudes HTTP entrantes y las incluye en los mensajes registrados dentro de cada solicitud.
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')
// Imagine any chain of async operations here
setImmediate(() => {
logWithId('finish')
res.end()
})
})
})
.listen(8080)
http.get('http://localhost:8080')
http.get('http://localhost:8080')
// Prints:
// 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')
// Imagine any chain of async operations here
setImmediate(() => {
logWithId('finish')
res.end()
})
})
})
.listen(8080)
http.get('http://localhost:8080')
http.get('http://localhost:8080')
// Prints:
// 0: start
// 1: start
// 0: finish
// 1: finish
Cada instancia de AsyncLocalStorage
mantiene un contexto de almacenamiento independiente. Pueden existir múltiples instancias simultáneamente sin riesgo de interferir con los datos de cada una.
new AsyncLocalStorage()
[Historial]
Versión | Cambios |
---|---|
v19.7.0, v18.16.0 | Se eliminó la opción experimental onPropagate . |
v19.2.0, v18.13.0 | Se agregó la opción onPropagate . |
v13.10.0, v12.17.0 | Añadido en: v13.10.0, v12.17.0 |
Crea una nueva instancia de AsyncLocalStorage
. El almacenamiento solo se proporciona dentro de una llamada run()
o después de una llamada enterWith()
.
Método estático: AsyncLocalStorage.bind(fn)
Añadido en: v19.8.0, v18.16.0
[Estable: 1 - Experimental]
Estable: 1 Estabilidad: 1 - Experimental
fn
<Función> La función que se enlazará al contexto de ejecución actual.- Devuelve: <Función> Una nueva función que llama a
fn
dentro del contexto de ejecución capturado.
Enlaza la función dada al contexto de ejecución actual.
Método estático: AsyncLocalStorage.snapshot()
Añadido en: v19.8.0, v18.16.0
[Estable: 1 - Experimental]
Estable: 1 Estabilidad: 1 - Experimental
- Devuelve: <Function> Una nueva función con la firma
(fn: (...args) : R, ...args) : R
.
Captura el contexto de ejecución actual y devuelve una función que acepta una función como argumento. Siempre que se llama a la función devuelta, llama a la función que se le pasa dentro del contexto capturado.
const asyncLocalStorage = new AsyncLocalStorage()
const runInAsyncScope = asyncLocalStorage.run(123, () => AsyncLocalStorage.snapshot())
const result = asyncLocalStorage.run(321, () => runInAsyncScope(() => asyncLocalStorage.getStore()))
console.log(result) // devuelve 123
AsyncLocalStorage.snapshot() puede reemplazar el uso de AsyncResource para propósitos simples de seguimiento de contexto asincrónico, por ejemplo:
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())) // devuelve 123
asyncLocalStorage.disable()
Añadido en: v13.10.0, v12.17.0
[Estable: 1 - Experimental]
Estable: 1 Estabilidad: 1 - Experimental
Desactiva la instancia de AsyncLocalStorage
. Todas las llamadas posteriores a asyncLocalStorage.getStore()
devolverán undefined
hasta que se llame de nuevo a asyncLocalStorage.run()
o asyncLocalStorage.enterWith()
.
Al llamar a asyncLocalStorage.disable()
, se saldrá de todos los contextos actuales vinculados a la instancia.
Es necesario llamar a asyncLocalStorage.disable()
antes de que asyncLocalStorage
pueda ser recolectado por el garbage collector. Esto no se aplica a los almacenes proporcionados por asyncLocalStorage
, ya que esos objetos se recolectan junto con los recursos asincrónicos correspondientes.
Utilice este método cuando asyncLocalStorage
ya no esté en uso en el proceso actual.
asyncLocalStorage.getStore()
Añadido en: v13.10.0, v12.17.0
- Devuelve: <cualquiera>
Devuelve el almacén actual. Si se llama fuera de un contexto asincrónico inicializado llamando a asyncLocalStorage.run()
o asyncLocalStorage.enterWith()
, devuelve undefined
.
asyncLocalStorage.enterWith(store)
Añadido en: v13.11.0, v12.17.0
[Estable: 1 - Experimental]
Estable: 1 Estabilidad: 1 - Experimental
store
<any>
Transiciona al contexto durante el resto de la ejecución síncrona actual y luego persiste el almacén a través de cualquier llamada asíncrona posterior.
Ejemplo:
const store = { id: 1 }
// Reemplaza el almacén anterior con el objeto de almacén dado
asyncLocalStorage.enterWith(store)
asyncLocalStorage.getStore() // Devuelve el objeto de almacén
someAsyncOperation(() => {
asyncLocalStorage.getStore() // Devuelve el mismo objeto
})
Esta transición continuará durante toda la ejecución síncrona. Esto significa que si, por ejemplo, se ingresa al contexto dentro de un controlador de eventos, los controladores de eventos posteriores también se ejecutarán dentro de ese contexto a menos que estén específicamente vinculados a otro contexto con un AsyncResource
. Es por eso que se debe preferir run()
sobre enterWith()
a menos que existan razones de peso para usar el último método.
const store = { id: 1 }
emitter.on('my-event', () => {
asyncLocalStorage.enterWith(store)
})
emitter.on('my-event', () => {
asyncLocalStorage.getStore() // Devuelve el mismo objeto
})
asyncLocalStorage.getStore() // Devuelve indefinido
emitter.emit('my-event')
asyncLocalStorage.getStore() // Devuelve el mismo objeto
asyncLocalStorage.run(store, callback[, ...args])
Añadido en: v13.10.0, v12.17.0
store
<any>callback
<Function>...args
<any>
Ejecuta una función sincrónicamente dentro de un contexto y devuelve su valor de retorno. El almacén no es accesible fuera de la función de devolución de llamada. El almacén es accesible para cualquier operación asíncrona creada dentro de la devolución de llamada.
Los argumentos opcionales args
se pasan a la función de devolución de llamada.
Si la función de devolución de llamada lanza un error, la función run()
también lanza el error. El rastreo de la pila no se ve afectado por esta llamada y se sale del contexto.
Ejemplo:
const store = { id: 2 }
try {
asyncLocalStorage.run(store, () => {
asyncLocalStorage.getStore() // Devuelve el objeto store
setTimeout(() => {
asyncLocalStorage.getStore() // Devuelve el objeto store
}, 200)
throw new Error()
})
} catch (e) {
asyncLocalStorage.getStore() // Devuelve undefined
// El error será capturado aquí
}
asyncLocalStorage.exit(callback[, ...args])
Añadido en: v13.10.0, v12.17.0
[Estable: 1 - Experimental]
Estable: 1 Estabilidad: 1 - Experimental
callback
<Function>...args
<any>
Ejecuta una función sincrónicamente fuera de un contexto y devuelve su valor de retorno. El almacén no es accesible dentro de la función de devolución de llamada o las operaciones asíncronas creadas dentro de la devolución de llamada. Cualquier llamada a getStore()
realizada dentro de la función de devolución de llamada siempre devolverá undefined
.
Los argumentos opcionales args
se pasan a la función de devolución de llamada.
Si la función de devolución de llamada lanza un error, la función exit()
también lanza el error. El rastreo de pila no se ve afectado por esta llamada y se vuelve a entrar en el contexto.
Ejemplo:
// Dentro de una llamada a run
try {
asyncLocalStorage.getStore() // Devuelve el objeto o valor del almacén
asyncLocalStorage.exit(() => {
asyncLocalStorage.getStore() // Devuelve undefined
throw new Error()
})
} catch (e) {
asyncLocalStorage.getStore() // Devuelve el mismo objeto o valor
// El error será capturado aquí
}
Uso con async/await
Si, dentro de una función async
, solo se debe ejecutar una llamada await
dentro de un contexto, se debe usar el siguiente patrón:
async function fn() {
await asyncLocalStorage.run(new Map(), () => {
asyncLocalStorage.getStore().set('key', value)
return foo() // El valor de retorno de foo será esperado
})
}
En este ejemplo, el almacén solo está disponible en la función de devolución de llamada y las funciones llamadas por foo
. Fuera de run
, llamar a getStore
devolverá undefined
.
Solución de problemas: Pérdida de contexto
En la mayoría de los casos, AsyncLocalStorage
funciona sin problemas. En situaciones excepcionales, el almacén actual se pierde en una de las operaciones asíncronas.
Si su código está basado en devoluciones de llamada, basta con convertirlo en promesas con util.promisify()
para que empiece a funcionar con promesas nativas.
Si necesita usar una API basada en devoluciones de llamada o su código asume una implementación thenable personalizada, use la clase AsyncResource
para asociar la operación asíncrona con el contexto de ejecución correcto. Encuentre la llamada a la función responsable de la pérdida de contexto registrando el contenido de asyncLocalStorage.getStore()
después de las llamadas que sospecha que son responsables de la pérdida. Cuando el código registre undefined
, la última devolución de llamada llamada es probablemente la responsable de la pérdida de contexto.
Clase: AsyncResource
[Historial]
Versión | Cambios |
---|---|
v16.4.0 | AsyncResource ahora es Estable. Anteriormente, había sido Experimental. |
La clase AsyncResource
está diseñada para ser extendida por los recursos asincrónicos del integrador. Usando esto, los usuarios pueden fácilmente activar los eventos de ciclo de vida de sus propios recursos.
El gancho init
se activará cuando se cree una instancia de AsyncResource
.
A continuación se muestra una descripción general de la API de AsyncResource
.
import { AsyncResource, executionAsyncId } from 'node:async_hooks'
// AsyncResource() está diseñado para ser extendido. Instanciar un
// nuevo AsyncResource() también activa init. Si se omite triggerAsyncId, entonces
// async_hook.executionAsyncId() es usado.
const asyncResource = new AsyncResource(type, { triggerAsyncId: executionAsyncId(), requireManualDestroy: false })
// Ejecuta una función en el contexto de ejecución del recurso. Esto hará
// * establecer el contexto del recurso
// * activar las devoluciones de llamada de AsyncHooks antes
// * llamar a la función proporcionada `fn` con los argumentos suministrados
// * activar las devoluciones de llamada de AsyncHooks después
// * restaurar el contexto de ejecución original
asyncResource.runInAsyncScope(fn, thisArg, ...args)
// Llamar a las devoluciones de llamada de destrucción de AsyncHooks.
asyncResource.emitDestroy()
// Devuelve el ID único asignado a la instancia de AsyncResource.
asyncResource.asyncId()
// Devuelve el ID de activación para la instancia de AsyncResource.
asyncResource.triggerAsyncId()
const { AsyncResource, executionAsyncId } = require('node:async_hooks')
// AsyncResource() está diseñado para ser extendido. Instanciar un
// nuevo AsyncResource() también activa init. Si se omite triggerAsyncId, entonces
// async_hook.executionAsyncId() es usado.
const asyncResource = new AsyncResource(type, { triggerAsyncId: executionAsyncId(), requireManualDestroy: false })
// Ejecuta una función en el contexto de ejecución del recurso. Esto hará
// * establecer el contexto del recurso
// * activar las devoluciones de llamada de AsyncHooks antes
// * llamar a la función proporcionada `fn` con los argumentos suministrados
// * activar las devoluciones de llamada de AsyncHooks después
// * restaurar el contexto de ejecución original
asyncResource.runInAsyncScope(fn, thisArg, ...args)
// Llamar a las devoluciones de llamada de destrucción de AsyncHooks.
asyncResource.emitDestroy()
// Devuelve el ID único asignado a la instancia de AsyncResource.
asyncResource.asyncId()
// Devuelve el ID de activación para la instancia de AsyncResource.
asyncResource.triggerAsyncId()
new AsyncResource(type[, options])
type
<string> El tipo de evento asincrónico.options
<Object>triggerAsyncId
<number> El ID del contexto de ejecución que creó este evento asincrónico. Predeterminado:executionAsyncId()
.requireManualDestroy
<boolean> Si se establece entrue
, deshabilitaemitDestroy
cuando el objeto se recoge como basura. Normalmente no es necesario establecerlo (incluso siemitDestroy
se llama manualmente), a menos que se recupere elasyncId
del recurso y se llame aemitDestroy
de las API sensibles con él. Cuando se establece enfalse
, la llamadaemitDestroy
en la recolección de basura solo tendrá lugar si hay al menos un ganchodestroy
activo. Predeterminado:false
.
Ejemplo de uso:
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étodo estático: AsyncResource.bind(fn[, type[, thisArg]])
[Historial]
Versión | Cambios |
---|---|
v20.0.0 | La propiedad asyncResource agregada a la función enlazada ha quedado en desuso y se eliminará en una versión futura. |
v17.8.0, v16.15.0 | Se cambió el valor predeterminado cuando thisArg es indefinido para usar this del llamador. |
v16.0.0 | Se agregó thisArg opcional. |
v14.8.0, v12.19.0 | Añadido en: v14.8.0, v12.19.0 |
fn
<Function> La función que se enlazará al contexto de ejecución actual.type
<string> Un nombre opcional para asociar con elAsyncResource
subyacente.thisArg
<any>
Enlaza la función dada al contexto de ejecución actual.
asyncResource.bind(fn[, thisArg])
[Historial]
Versión | Cambios |
---|---|
v20.0.0 | La propiedad asyncResource agregada a la función enlazada ha quedado en desuso y se eliminará en una versión futura. |
v17.8.0, v16.15.0 | Se cambió el valor predeterminado cuando thisArg es indefinido para usar this del llamador. |
v16.0.0 | Se agregó thisArg opcional. |
v14.8.0, v12.19.0 | Añadido en: v14.8.0, v12.19.0 |
fn
<Function> La función que se enlazará para ejecutar en el ámbito de esteAsyncResource
.thisArg
<any>
Enlaza la función dada para que se ejecute en el ámbito de este AsyncResource
.
asyncResource.runInAsyncScope(fn[, thisArg, ...args])
Añadido en: v9.6.0
fn
<Function> La función que se llamará en el contexto de ejecución de este recurso asincrónico.thisArg
<any> El receptor que se utilizará para la llamada a la función....args
<any> Argumentos opcionales que se pasarán a la función.
Llama a la función proporcionada con los argumentos proporcionados en el contexto de ejecución del recurso asincrónico. Esto establecerá el contexto, activará los AsyncHooks antes de las devoluciones de llamada, llamará a la función, activará los AsyncHooks después de las devoluciones de llamada y luego restaurará el contexto de ejecución original.
asyncResource.emitDestroy()
- Devuelve: <AsyncResource> Una referencia a
asyncResource
.
Llama a todos los ganchos destroy
. Esto solo debe llamarse una vez. Se lanzará un error si se llama más de una vez. Esto debe llamarse manualmente. Si se deja que el recurso sea recolectado por el GC, entonces los ganchos destroy
nunca se llamarán.
asyncResource.asyncId()
- Devuelve: <number> El
asyncId
único asignado al recurso.
asyncResource.triggerAsyncId()
- Devuelve: <number> El mismo
triggerAsyncId
que se pasa al constructorAsyncResource
.
Uso de AsyncResource
para un grupo de subprocesos Worker
El siguiente ejemplo muestra cómo usar la clase AsyncResource
para proporcionar correctamente el seguimiento asincrónico de un grupo de Worker
. Otros grupos de recursos, como los grupos de conexiones a bases de datos, pueden seguir un modelo similar.
Suponiendo que la tarea es sumar dos números, usando un archivo llamado task_processor.js
con el siguiente contenido:
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 grupo de Worker alrededor de él podría usar la siguiente estructura:
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
Sin el seguimiento explícito agregado por los objetos WorkerPoolTaskInfo
, parecería que las funciones de devolución de llamada están asociadas con los objetos Worker
individuales. Sin embargo, la creación de los Worker
no está asociada con la creación de las tareas y no proporciona información sobre cuándo se programaron las tareas.
Este grupo podría usarse de la siguiente manera:
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()
})
}
Integrando AsyncResource
con EventEmitter
Los listeners de eventos activados por un EventEmitter
pueden ejecutarse en un contexto de ejecución diferente al que estaba activo cuando se llamó a eventEmitter.on()
.
El siguiente ejemplo muestra cómo usar la clase AsyncResource
para asociar correctamente un listener de eventos con el contexto de ejecución correcto. El mismo enfoque se puede aplicar a un Stream
o una clase similar basada en eventos.
import { createServer } from 'node:http'
import { AsyncResource, executionAsyncId } from 'node:async_hooks'
const server = createServer((req, res) => {
req.on(
'close',
AsyncResource.bind(() => {
// El contexto de ejecución está enlazado al ámbito externo actual.
})
)
req.on('close', () => {
// El contexto de ejecución está enlazado al ámbito que provocó la emisión 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(() => {
// El contexto de ejecución está enlazado al ámbito externo actual.
})
)
req.on('close', () => {
// El contexto de ejecución está enlazado al ámbito que provocó la emisión de 'close'.
})
res.end()
}).listen(3000)