Skip to content

Ganchos asincrónicos

[Estable: 1 - Experimental]

Estable: 1 Estabilidad: 1 - Experimental. Por favor, migre fuera de esta API si puede. No recomendamos el uso de las API createHook, AsyncHook y executionAsyncResource ya que tienen problemas de usabilidad, riesgos de seguridad e implicaciones de rendimiento. La API estable AsyncLocalStorage ofrece una mejor solución para los casos de uso de seguimiento de contexto asincrónico. Si tiene un caso de uso para createHook, AsyncHook o executionAsyncResource más allá de la necesidad de seguimiento de contexto resuelta por AsyncLocalStorage o los datos de diagnóstico proporcionados actualmente por Canal de diagnóstico, abra un problema en https://github.com/nodejs/node/issues describiendo su caso de uso para que podamos crear una API más enfocada a un propósito específico.

Código fuente: lib/async_hooks.js

Desaconsejamos encarecidamente el uso de la API async_hooks. Otras API que pueden cubrir la mayoría de sus casos de uso incluyen:

El módulo node:async_hooks proporciona una API para rastrear recursos asincrónicos. Se puede acceder a él usando:

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

Terminología

Un recurso asíncrono representa un objeto con un callback asociado. Este callback puede ser llamado múltiples veces, como el evento 'connection' en net.createServer(), o solo una vez como en fs.open(). Un recurso también puede ser cerrado antes de que el callback sea llamado. AsyncHook no distingue explícitamente entre estos diferentes casos, pero los representará como el concepto abstracto que es un recurso.

Si se utilizan Workers, cada hilo tiene una interfaz async_hooks independiente, y cada hilo usará un nuevo conjunto de IDs asíncronos.

Visión general

A continuación se muestra una visión general simple de la API pública.

js
import async_hooks from 'node:async_hooks'

// Devuelve el ID del contexto de ejecución actual.
const eid = async_hooks.executionAsyncId()

// Devuelve el ID del manejador responsable de desencadenar el callback del
// ámbito de ejecución actual que se va a llamar.
const tid = async_hooks.triggerAsyncId()

// Crea una nueva instancia AsyncHook. Todos estos callbacks son opcionales.
const asyncHook = async_hooks.createHook({ init, before, after, destroy, promiseResolve })

// Permite que los callbacks de esta instancia AsyncHook se llamen. Esta no es una
// acción implícita después de ejecutar el constructor, y debe ejecutarse
// explícitamente para comenzar a ejecutar los callbacks.
asyncHook.enable()

// Desactiva la escucha de nuevos eventos asíncronos.
asyncHook.disable()

//
// Los siguientes son los callbacks que se pueden pasar a createHook().
//

// init() se llama durante la construcción del objeto. Es posible que el recurso
// no haya completado la construcción cuando se ejecuta este callback. Por lo
// tanto, es posible que no se hayan rellenado todos los campos del recurso
// referenciado por "asyncId".
function init(asyncId, type, triggerAsyncId, resource) {}

// before() se llama justo antes de que se llame al callback del recurso. Se
// puede llamar 0-N veces para los manejadores (como TCPWrap), y se llamará
// exactamente 1 vez para las solicitudes (como FSReqCallback).
function before(asyncId) {}

// after() se llama justo después de que haya finalizado el callback del recurso.
function after(asyncId) {}

// destroy() se llama cuando el recurso se destruye.
function destroy(asyncId) {}

// promiseResolve() se llama solo para recursos de promesas, cuando se invoca la
// función resolve() pasada al constructor de Promise (ya sea directamente o a
// través de otros medios de resolución de una promesa).
function promiseResolve(asyncId) {}
js
const async_hooks = require('node:async_hooks')

// Devuelve el ID del contexto de ejecución actual.
const eid = async_hooks.executionAsyncId()

// Devuelve el ID del manejador responsable de desencadenar el callback del
// ámbito de ejecución actual que se va a llamar.
const tid = async_hooks.triggerAsyncId()

// Crea una nueva instancia AsyncHook. Todos estos callbacks son opcionales.
const asyncHook = async_hooks.createHook({ init, before, after, destroy, promiseResolve })

// Permite que los callbacks de esta instancia AsyncHook se llamen. Esta no es una
// acción implícita después de ejecutar el constructor, y debe ejecutarse
// explícitamente para comenzar a ejecutar los callbacks.
asyncHook.enable()

// Desactiva la escucha de nuevos eventos asíncronos.
asyncHook.disable()

//
// Los siguientes son los callbacks que se pueden pasar a createHook().
//

// init() se llama durante la construcción del objeto. Es posible que el recurso
// no haya completado la construcción cuando se ejecuta este callback. Por lo
// tanto, es posible que no se hayan rellenado todos los campos del recurso
// referenciado por "asyncId".
function init(asyncId, type, triggerAsyncId, resource) {}

// before() se llama justo antes de que se llame al callback del recurso. Se
// puede llamar 0-N veces para los manejadores (como TCPWrap), y se llamará
// exactamente 1 vez para las solicitudes (como FSReqCallback).
function before(asyncId) {}

// after() se llama justo después de que haya finalizado el callback del recurso.
function after(asyncId) {}

// destroy() se llama cuando el recurso se destruye.
function destroy(asyncId) {}

// promiseResolve() se llama solo para recursos de promesas, cuando se invoca la
// función resolve() pasada al constructor de Promise (ya sea directamente o a
// través de otros medios de resolución de una promesa).
function promiseResolve(asyncId) {}

async_hooks.createHook(callbacks)

Añadido en: v8.1.0

Registra funciones que se llamarán para diferentes eventos de duración de cada operación asincrónica.

Las devoluciones de llamada init()/before()/after()/destroy() se llaman para el evento asincrónico respectivo durante la vida útil de un recurso.

Todas las devoluciones de llamada son opcionales. Por ejemplo, si solo se necesita rastrear la limpieza de recursos, solo se necesita pasar la devolución de llamada destroy. Los detalles específicos de todas las funciones que se pueden pasar a callbacks están en la sección Devoluciones de llamada del gancho.

js
import { createHook } from 'node:async_hooks'

const asyncHook = createHook({
  init(asyncId, type, triggerAsyncId, resource) {},
  destroy(asyncId) {},
})
js
const async_hooks = require('node:async_hooks')

const asyncHook = async_hooks.createHook({
  init(asyncId, type, triggerAsyncId, resource) {},
  destroy(asyncId) {},
})

Las devoluciones de llamada se heredarán a través de la cadena de prototipos:

js
class MyAsyncCallbacks {
  init(asyncId, type, triggerAsyncId, resource) {}
  destroy(asyncId) {}
}

class MyAddedCallbacks extends MyAsyncCallbacks {
  before(asyncId) {}
  after(asyncId) {}
}

const asyncHook = async_hooks.createHook(new MyAddedCallbacks())

Debido a que las promesas son recursos asincrónicos cuyo ciclo de vida se rastrea a través del mecanismo de ganchos asincrónicos, las devoluciones de llamada init(), before(), after() y destroy() no deben ser funciones asincrónicas que devuelvan promesas.

Manejo de errores

Si alguna devolución de llamada de AsyncHook lanza una excepción, la aplicación imprimirá el rastreo de la pila y se cerrará. La ruta de salida sigue la de una excepción no capturada, pero todos los oyentes de 'uncaughtException' se eliminan, forzando así la salida del proceso. Las devoluciones de llamada de 'exit' seguirán llamándose a menos que la aplicación se ejecute con --abort-on-uncaught-exception, en cuyo caso se imprimirá un rastreo de la pila y la aplicación se cerrará, dejando un archivo central.

La razón de este comportamiento de manejo de errores es que estas devoluciones de llamada se ejecutan en puntos potencialmente volátiles de la vida útil de un objeto, por ejemplo, durante la construcción y destrucción de clases. Debido a esto, se considera necesario detener el proceso rápidamente para evitar un aborto involuntario en el futuro. Esto está sujeto a cambios en el futuro si se realiza un análisis exhaustivo para garantizar que una excepción pueda seguir el flujo de control normal sin efectos secundarios involuntarios.

Impresión en devoluciones de llamada de AsyncHook

Debido a que imprimir en la consola es una operación asíncrona, console.log() hará que se llamen las devoluciones de llamada de AsyncHook. Usar console.log() u operaciones asíncronas similares dentro de una función de devolución de llamada AsyncHook causará una recursión infinita. Una solución fácil para esto al depurar es usar una operación de registro síncrona como fs.writeFileSync(file, msg, flag). Esto imprimirá en el archivo y no invocará AsyncHook recursivamente porque es síncrono.

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

function debug(...args) {
  // Use una función como esta al depurar dentro de una devolución de llamada AsyncHook
  writeFileSync('log.out', `${format(...args)}\n`, { flag: 'a' })
}
js
const fs = require('node:fs')
const util = require('node:util')

function debug(...args) {
  // Use una función como esta al depurar dentro de una devolución de llamada AsyncHook
  fs.writeFileSync('log.out', `${util.format(...args)}\n`, { flag: 'a' })
}

Si se necesita una operación asíncrona para el registro, es posible realizar un seguimiento de lo que causó la operación asíncrona utilizando la información proporcionada por el propio AsyncHook. El registro debe omitirse entonces cuando fue el propio registro lo que provocó la llamada a la devolución de llamada AsyncHook. Al hacer esto, se rompe la recursión infinita.

Clase: AsyncHook

La clase AsyncHook expone una interfaz para rastrear eventos de ciclo de vida de operaciones asíncronas.

asyncHook.enable()

Habilita las funciones de devolución de llamada para una instancia AsyncHook dada. Si no se proporcionan funciones de devolución de llamada, la habilitación no tiene efecto.

La instancia AsyncHook está deshabilitada por defecto. Si la instancia AsyncHook debe habilitarse inmediatamente después de su creación, se puede usar el siguiente patrón.

js
import { createHook } from 'node:async_hooks'

const hook = createHook(callbacks).enable()
js
const async_hooks = require('node:async_hooks')

const hook = async_hooks.createHook(callbacks).enable()

asyncHook.disable()

Deshabilita las funciones de devolución de llamada para una instancia AsyncHook dada del grupo global de funciones de devolución de llamada AsyncHook que se ejecutarán. Una vez que una función de enganche se ha deshabilitado, no se volverá a llamar hasta que se habilite.

Para la consistencia de la API, disable() también devuelve la instancia AsyncHook.

Ganchos de devolución de llamada

Los eventos clave en la vida útil de los eventos asíncronos se han categorizado en cuatro áreas: instanciación, antes/después de que se llame a la devolución de llamada y cuando se destruye la instancia.

init(asyncId, type, triggerAsyncId, resource)

  • asyncId <number> Un ID único para el recurso asíncrono.
  • type <string> El tipo de recurso asíncrono.
  • triggerAsyncId <number> El ID único del recurso asíncrono en cuyo contexto de ejecución se creó este recurso asíncrono.
  • resource <Object> Referencia al recurso que representa la operación asíncrona, debe liberarse durante destroy.

Se llama cuando se construye una clase que tiene la posibilidad de emitir un evento asíncrono. Esto no significa que la instancia deba llamar a before/after antes de que se llame a destroy, solo que existe la posibilidad.

Este comportamiento se puede observar haciendo algo como abrir un recurso y luego cerrarlo antes de que se pueda usar el recurso. El siguiente fragmento demuestra esto.

js
import { createServer } from 'node:net'

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

A cada nuevo recurso se le asigna un ID que es único dentro del ámbito de la instancia actual de Node.js.

type

El type es una cadena que identifica el tipo de recurso que provocó la llamada a init. En general, corresponderá al nombre del constructor del recurso.

El type de los recursos creados por el propio Node.js puede cambiar en cualquier versión de Node.js. Los valores válidos incluyen TLSWRAP, TCPWRAP, TCPSERVERWRAP, GETADDRINFOREQWRAP, FSREQCALLBACK, Microtask y Timeout. Inspeccione el código fuente de la versión de Node.js utilizada para obtener la lista completa.

Además, los usuarios de AsyncResource crean recursos asíncronos independientes del propio Node.js.

También existe el tipo de recurso PROMISE, que se utiliza para rastrear instancias de Promise y el trabajo asíncrono programado por ellas.

Los usuarios pueden definir su propio type cuando utilizan la API de incrustador pública.

Es posible tener colisiones de nombres de tipo. Se anima a los incrustadores a utilizar prefijos únicos, como el nombre del paquete npm, para evitar colisiones al escuchar los ganchos.

triggerAsyncId

triggerAsyncId es el asyncId del recurso que causó (o "desencadenó") la inicialización del nuevo recurso y que provocó la llamada a init. Esto es diferente de async_hooks.executionAsyncId(), que solo muestra cuándo se creó un recurso, mientras que triggerAsyncId muestra por qué se creó un recurso.

La siguiente es una demostración simple de triggerAsyncId:

js
import { createHook, executionAsyncId } from 'node:async_hooks'
import { stdout } from 'node:process'
import net from 'node:net'
import fs from 'node:fs'

createHook({
  init(asyncId, type, triggerAsyncId) {
    const eid = executionAsyncId()
    fs.writeSync(stdout.fd, `${type}(${asyncId}): trigger: ${triggerAsyncId} execution: ${eid}\n`)
  },
}).enable()

net.createServer(conn => {}).listen(8080)
js
const { createHook, executionAsyncId } = require('node:async_hooks')
const { stdout } = require('node:process')
const net = require('node:net')
const fs = require('node:fs')

createHook({
  init(asyncId, type, triggerAsyncId) {
    const eid = executionAsyncId()
    fs.writeSync(stdout.fd, `${type}(${asyncId}): trigger: ${triggerAsyncId} execution: ${eid}\n`)
  },
}).enable()

net.createServer(conn => {}).listen(8080)

Salida al acceder al servidor con nc localhost 8080:

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

El TCPSERVERWRAP es el servidor que recibe las conexiones.

El TCPWRAP es la nueva conexión del cliente. Cuando se realiza una nueva conexión, la instancia TCPWrap se construye inmediatamente. Esto sucede fuera de cualquier pila de JavaScript. (Un executionAsyncId() de 0 significa que se está ejecutando desde C++ sin una pila de JavaScript encima). Con solo esa información, sería imposible vincular los recursos en términos de qué los causó para ser creados, por lo que triggerAsyncId recibe la tarea de propagar qué recurso es responsable de la existencia del nuevo recurso.

resource

resource es un objeto que representa el recurso asíncrono real que se ha inicializado. La API para acceder al objeto puede ser especificada por el creador del recurso. Los recursos creados por el propio Node.js son internos y pueden cambiar en cualquier momento. Por lo tanto, no se especifica ninguna API para estos.

En algunos casos, el objeto de recurso se reutiliza por razones de rendimiento, por lo que no es seguro usarlo como clave en un WeakMap o agregarle propiedades.

Ejemplo de contexto asíncrono

El caso de uso de seguimiento de contexto está cubierto por la API estable AsyncLocalStorage. Este ejemplo solo ilustra el funcionamiento de los ganchos asíncronos, pero AsyncLocalStorage se adapta mejor a este caso de uso.

El siguiente es un ejemplo con información adicional sobre las llamadas a init entre las llamadas before y after, específicamente cómo se verá la devolución de llamada a listen(). El formato de salida es ligeramente más elaborado para facilitar el contexto de llamada.

js
import async_hooks from 'node:async_hooks'
import fs from 'node:fs'
import net from 'node:net'
import { stdout } from 'node:process'
const { fd } = stdout

let indent = 0
async_hooks
  .createHook({
    init(asyncId, type, triggerAsyncId) {
      const eid = async_hooks.executionAsyncId()
      const indentStr = ' '.repeat(indent)
      fs.writeSync(fd, `${indentStr}${type}(${asyncId}):` + ` trigger: ${triggerAsyncId} execution: ${eid}\n`)
    },
    before(asyncId) {
      const indentStr = ' '.repeat(indent)
      fs.writeSync(fd, `${indentStr}before:  ${asyncId}\n`)
      indent += 2
    },
    after(asyncId) {
      indent -= 2
      const indentStr = ' '.repeat(indent)
      fs.writeSync(fd, `${indentStr}after:  ${asyncId}\n`)
    },
    destroy(asyncId) {
      const indentStr = ' '.repeat(indent)
      fs.writeSync(fd, `${indentStr}destroy:  ${asyncId}\n`)
    },
  })
  .enable()

net
  .createServer(() => {})
  .listen(8080, () => {
    // Esperemos 10 ms antes de registrar que el servidor se inició.
    setTimeout(() => {
      console.log('>>>', async_hooks.executionAsyncId())
    }, 10)
  })
js
const async_hooks = require('node:async_hooks')
const fs = require('node:fs')
const net = require('node:net')
const { fd } = process.stdout

let indent = 0
async_hooks
  .createHook({
    init(asyncId, type, triggerAsyncId) {
      const eid = async_hooks.executionAsyncId()
      const indentStr = ' '.repeat(indent)
      fs.writeSync(fd, `${indentStr}${type}(${asyncId}):` + ` trigger: ${triggerAsyncId} execution: ${eid}\n`)
    },
    before(asyncId) {
      const indentStr = ' '.repeat(indent)
      fs.writeSync(fd, `${indentStr}before:  ${asyncId}\n`)
      indent += 2
    },
    after(asyncId) {
      indent -= 2
      const indentStr = ' '.repeat(indent)
      fs.writeSync(fd, `${indentStr}after:  ${asyncId}\n`)
    },
    destroy(asyncId) {
      const indentStr = ' '.repeat(indent)
      fs.writeSync(fd, `${indentStr}destroy:  ${asyncId}\n`)
    },
  })
  .enable()

net
  .createServer(() => {})
  .listen(8080, () => {
    // Esperemos 10 ms antes de registrar que el servidor se inició.
    setTimeout(() => {
      console.log('>>>', async_hooks.executionAsyncId())
    }, 10)
  })

Salida solo iniciando el servidor:

bash
TCPSERVERWRAP(5): trigger: 1 execution: 1
TickObject(6): trigger: 5 execution: 1
before:  6
  Timeout(7): trigger: 6 execution: 6
after:   6
destroy: 6
before:  7
>>> 7
  TickObject(8): trigger: 7 execution: 7
after:   7
before:  8
after:   8

Como se ilustra en el ejemplo, executionAsyncId() y execution especifican cada uno el valor del contexto de ejecución actual; que está delimitado por las llamadas a before y after.

Solo usar execution para graficar la asignación de recursos resulta en lo siguiente:

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

El TCPSERVERWRAP no es parte de este gráfico, a pesar de que fue la razón por la que se llamó a console.log(). Esto se debe a que la vinculación a un puerto sin un nombre de host es una operación síncrona, pero para mantener una API completamente asíncrona, la devolución de llamada del usuario se coloca en un process.nextTick(). Por eso TickObject está presente en la salida y es un 'padre' para la devolución de llamada .listen().

El gráfico solo muestra cuándo se creó un recurso, no por qué, por lo que para rastrear el por qué use triggerAsyncId. Que puede representarse con el siguiente gráfico:

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

before(asyncId)

Cuando se inicia una operación asíncrona (como un servidor TCP que recibe una nueva conexión) o se completa (como escribir datos en disco), se llama a una devolución de llamada para notificar al usuario. La devolución de llamada before se llama justo antes de que se ejecute dicha devolución de llamada. asyncId es el identificador único asignado al recurso que está a punto de ejecutar la devolución de llamada.

La devolución de llamada before se llamará de 0 a N veces. La devolución de llamada before normalmente se llamará 0 veces si la operación asíncrona se canceló o, por ejemplo, si un servidor TCP no recibe conexiones. Los recursos asíncronos persistentes, como un servidor TCP, normalmente llamarán a la devolución de llamada before varias veces, mientras que otras operaciones como fs.open() solo la llamarán una vez.

after(asyncId)

Se llama inmediatamente después de que se complete la devolución de llamada especificada en before.

Si se produce una excepción no controlada durante la ejecución de la devolución de llamada, entonces after se ejecutará después de que se emita el evento 'uncaughtException' o se ejecute el controlador de un domain.

destroy(asyncId)

Llamado después de que se destruye el recurso correspondiente a asyncId. También se llama de forma asíncrona desde la API del integrador emitDestroy().

Algunos recursos dependen de la recolección de basura para la limpieza, por lo que si se hace una referencia al objeto resource pasado a init, es posible que destroy nunca se llame, causando una fuga de memoria en la aplicación. Si el recurso no depende de la recolección de basura, esto no será un problema.

El uso del gancho destroy genera una sobrecarga adicional porque permite el seguimiento de instancias de Promise a través del recolector de basura.

promiseResolve(asyncId)

Añadido en: v8.6.0

Llamado cuando se invoca la función resolve pasada al constructor Promise (ya sea directamente o a través de otros medios de resolución de una promesa).

resolve() no realiza ningún trabajo sincrónico observable.

La Promise no está necesariamente cumplida o rechazada en este punto si la Promise se resolvió asumiendo el estado de otra Promise.

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

llama a las siguientes funciones de devolución de llamada:

text
init para PROMISE con id 5, id de activación: 1
  promise resolve 5      # corresponde a resolve(true)
init para PROMISE con id 6, id de activación: 5  # la Promise devuelta por then()
  before 6               # se ingresa a la función de devolución de llamada then()
  promise resolve 6      # la función de devolución de llamada then() resuelve la promesa devolviendo un valor
  after 6

async_hooks.executionAsyncResource()

Añadido en: v13.9.0, v12.17.0

  • Devuelve: <Object> El recurso que representa la ejecución actual. Útil para almacenar datos dentro del recurso.

Los objetos de recurso devueltos por executionAsyncResource() son con mayor frecuencia objetos de manejo internos de Node.js con APIs no documentadas. El uso de cualquier función o propiedad en el objeto probablemente bloqueará su aplicación y debe evitarse.

El uso de executionAsyncResource() en el contexto de ejecución de nivel superior devolverá un objeto vacío, ya que no hay ningún objeto de manejo o solicitud que usar, pero tener un objeto que represente el nivel superior puede ser útil.

js
import { open } from 'node:fs'
import { executionAsyncId, executionAsyncResource } from 'node:async_hooks'

console.log(executionAsyncId(), executionAsyncResource()) // 1 {}
open(new URL(import.meta.url), 'r', (err, fd) => {
  console.log(executionAsyncId(), executionAsyncResource()) // 7 FSReqWrap
})
js
const { open } = require('node:fs')
const { executionAsyncId, executionAsyncResource } = require('node:async_hooks')

console.log(executionAsyncId(), executionAsyncResource()) // 1 {}
open(__filename, 'r', (err, fd) => {
  console.log(executionAsyncId(), executionAsyncResource()) // 7 FSReqWrap
})

Esto se puede usar para implementar almacenamiento local de continuación sin el uso de un Map de seguimiento para almacenar los metadatos:

js
import { createServer } from 'node:http'
import { executionAsyncId, executionAsyncResource, createHook } from 'node:async_hooks'
const sym = Symbol('state') // Símbolo privado para evitar contaminación

createHook({
  init(asyncId, type, triggerAsyncId, resource) {
    const cr = executionAsyncResource()
    if (cr) {
      resource[sym] = cr[sym]
    }
  },
}).enable()

const server = createServer((req, res) => {
  executionAsyncResource()[sym] = { state: req.url }
  setTimeout(function () {
    res.end(JSON.stringify(executionAsyncResource()[sym]))
  }, 100)
}).listen(3000)
js
const { createServer } = require('node:http')
const { executionAsyncId, executionAsyncResource, createHook } = require('node:async_hooks')
const sym = Symbol('state') // Símbolo privado para evitar contaminación

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

[Historial]

VersiónCambios
v8.2.0Renombrado desde currentId.
v8.1.0Añadido en: v8.1.0
  • Devuelve: <number> El asyncId del contexto de ejecución actual. Útil para rastrear cuándo algo llama.
js
import { executionAsyncId } from 'node:async_hooks'
import fs from 'node:fs'

console.log(executionAsyncId()) // 1 - bootstrap
const path = '.'
fs.open(path, 'r', (err, fd) => {
  console.log(executionAsyncId()) // 6 - open()
})
js
const async_hooks = require('node:async_hooks')
const fs = require('node:fs')

console.log(async_hooks.executionAsyncId()) // 1 - bootstrap
const path = '.'
fs.open(path, 'r', (err, fd) => {
  console.log(async_hooks.executionAsyncId()) // 6 - open()
})

El ID devuelto por executionAsyncId() está relacionado con el tiempo de ejecución, no con la causalidad (que está cubierta por triggerAsyncId()):

js
const server = net
  .createServer(conn => {
    // Devuelve el ID del servidor, no de la nueva conexión, porque la
    // devolución de llamada se ejecuta en el ámbito de ejecución de MakeCallback() del servidor.
    async_hooks.executionAsyncId()
  })
  .listen(port, () => {
    // Devuelve el ID de un TickObject (process.nextTick()) porque todas
    // las devoluciones de llamada pasadas a .listen() están encapsuladas en un nextTick().
    async_hooks.executionAsyncId()
  })

Los contextos de Promise pueden no obtener executionAsyncIds precisos de forma predeterminada. Consulte la sección sobre seguimiento de la ejecución de promesas.

async_hooks.triggerAsyncId()

  • Devuelve: <number> El ID del recurso responsable de llamar a la devolución de llamada que se está ejecutando actualmente.
js
const server = net
  .createServer(conn => {
    // El recurso que causó (o desencadenó) que se llamara a esta devolución de llamada
    // fue el de la nueva conexión. Por lo tanto, el valor de retorno de triggerAsyncId()
    // es el asyncId de "conn".
    async_hooks.triggerAsyncId()
  })
  .listen(port, () => {
    // Aunque todas las devoluciones de llamada pasadas a .listen() están envueltas en un nextTick()
    // la devolución de llamada en sí existe porque se realizó la llamada a .listen() del servidor.
    // Entonces, el valor de retorno sería el ID del servidor.
    async_hooks.triggerAsyncId()
  })

Los contextos de Promise pueden no obtener triggerAsyncId válidos de forma predeterminada. Consulte la sección sobre seguimiento de la ejecución de promesas.

async_hooks.asyncWrapProviders

Añadido en: v17.2.0, v16.14.0

  • Devuelve: Un mapa de tipos de proveedor al ID numérico correspondiente. Este mapa contiene todos los tipos de eventos que podrían ser emitidos por el evento async_hooks.init().

Esta característica suprime el uso en desuso de process.binding('async_wrap').Providers. Ver: DEP0111

Seguimiento de la ejecución de promesas

De forma predeterminada, las ejecuciones de promesas no reciben asyncId debido a la naturaleza relativamente costosa de la API de introspección de promesas proporcionada por V8. Esto significa que los programas que utilizan promesas o async/await no obtendrán los identificadores de ejecución y activación correctos para los contextos de devolución de llamada de promesas de forma predeterminada.

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

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

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

Observe que la devolución de llamada then() afirma haberse ejecutado en el contexto del ámbito externo a pesar de que hubo un salto asíncrono. Además, el valor triggerAsyncId es 0, lo que significa que nos falta contexto sobre el recurso que causó (activó) la ejecución de la devolución de llamada then().

Instalar ganchos asíncronos a través de async_hooks.createHook habilita el seguimiento de la ejecución de promesas:

js
import { createHook, executionAsyncId, triggerAsyncId } from 'node:async_hooks'
createHook({ init() {} }).enable() // fuerza la habilitación de PromiseHooks.
Promise.resolve(1729).then(() => {
  console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`)
})
// produce:
// eid 7 tid 6
js
const { createHook, executionAsyncId, triggerAsyncId } = require('node:async_hooks')

createHook({ init() {} }).enable() // fuerza la habilitación de PromiseHooks.
Promise.resolve(1729).then(() => {
  console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`)
})
// produce:
// eid 7 tid 6

En este ejemplo, agregar cualquier función de gancho real habilitó el seguimiento de las promesas. Hay dos promesas en el ejemplo anterior; la promesa creada por Promise.resolve() y la promesa devuelta por la llamada a then(). En el ejemplo anterior, la primera promesa obtuvo el asyncId 6 y la última obtuvo el asyncId 7. Durante la ejecución de la devolución de llamada then(), nos estamos ejecutando en el contexto de la promesa con asyncId 7. Esta promesa fue activada por el recurso asíncrono 6.

Otra sutileza con las promesas es que las devoluciones de llamada before y after solo se ejecutan en promesas encadenadas. Esto significa que las promesas no creadas por then()/catch() no tendrán las devoluciones de llamada before y after disparadas en ellas. Para más detalles, consulte los detalles de la API de V8 PromiseHooks.

API de incrustación de JavaScript

Los desarrolladores de bibliotecas que manejan sus propios recursos asíncronos realizando tareas como E/S, agrupación de conexiones o administración de colas de devolución de llamada pueden usar la API de JavaScript AsyncResource para que se llamen todas las devoluciones de llamada apropiadas.

Clase: AsyncResource

La documentación de esta clase se ha movido a AsyncResource.

Clase: AsyncLocalStorage

La documentación de esta clase se ha movido a AsyncLocalStorage.