Async hooks
[Stable: 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. Los casos de uso de seguimiento del contexto asíncrono se gestionan mejor con la API estable AsyncLocalStorage
. Si tiene un caso de uso para createHook
, AsyncHook
o executionAsyncResource
más allá de la necesidad de seguimiento del 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 centrada en el propósito.
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:
AsyncLocalStorage
rastrea el contexto asíncronoprocess.getActiveResourcesInfo()
rastrea los recursos activos
El módulo node:async_hooks
proporciona una API para rastrear recursos asíncronos. Se puede acceder a él usando:
import async_hooks from 'node:async_hooks';
const async_hooks = require('node:async_hooks');
Terminología
Un recurso asíncrono representa un objeto con una devolución de llamada asociada. Esta devolución de llamada se puede llamar varias veces, como el evento 'connection'
en net.createServer()
, o solo una vez como en fs.open()
. Un recurso también se puede cerrar antes de que se llame a la devolución de llamada. AsyncHook
no distingue explícitamente entre estos diferentes casos, sino que los representará como el concepto abstracto que es un recurso.
Si se utilizan Worker
s, cada hilo tiene una interfaz async_hooks
independiente, y cada hilo utilizará un nuevo conjunto de ID asíncronos.
Visión general
A continuación se muestra una descripción general simple de la API pública.
import async_hooks from 'node:async_hooks';
// Return the ID of the current execution context.
const eid = async_hooks.executionAsyncId();
// Return the ID of the handle responsible for triggering the callback of the
// current execution scope to call.
const tid = async_hooks.triggerAsyncId();
// Create a new AsyncHook instance. All of these callbacks are optional.
const asyncHook =
async_hooks.createHook({ init, before, after, destroy, promiseResolve });
// Allow callbacks of this AsyncHook instance to call. This is not an implicit
// action after running the constructor, and must be explicitly run to begin
// executing callbacks.
asyncHook.enable();
// Disable listening for new asynchronous events.
asyncHook.disable();
//
// The following are the callbacks that can be passed to createHook().
//
// init() is called during object construction. The resource may not have
// completed construction when this callback runs. Therefore, all fields of the
// resource referenced by "asyncId" may not have been populated.
function init(asyncId, type, triggerAsyncId, resource) { }
// before() is called just before the resource's callback is called. It can be
// called 0-N times for handles (such as TCPWrap), and will be called exactly 1
// time for requests (such as FSReqCallback).
function before(asyncId) { }
// after() is called just after the resource's callback has finished.
function after(asyncId) { }
// destroy() is called when the resource is destroyed.
function destroy(asyncId) { }
// promiseResolve() is called only for promise resources, when the
// resolve() function passed to the Promise constructor is invoked
// (either directly or through other means of resolving a promise).
function promiseResolve(asyncId) { }
const async_hooks = require('node:async_hooks');
// Return the ID of the current execution context.
const eid = async_hooks.executionAsyncId();
// Return the ID of the handle responsible for triggering the callback of the
// current execution scope to call.
const tid = async_hooks.triggerAsyncId();
// Create a new AsyncHook instance. All of these callbacks are optional.
const asyncHook =
async_hooks.createHook({ init, before, after, destroy, promiseResolve });
// Allow callbacks of this AsyncHook instance to call. This is not an implicit
// action after running the constructor, and must be explicitly run to begin
// executing callbacks.
asyncHook.enable();
// Disable listening for new asynchronous events.
asyncHook.disable();
//
// The following are the callbacks that can be passed to createHook().
//
// init() is called during object construction. The resource may not have
// completed construction when this callback runs. Therefore, all fields of the
// resource referenced by "asyncId" may not have been populated.
function init(asyncId, type, triggerAsyncId, resource) { }
// before() is called just before the resource's callback is called. It can be
// called 0-N times for handles (such as TCPWrap), and will be called exactly 1
// time for requests (such as FSReqCallback).
function before(asyncId) { }
// after() is called just after the resource's callback has finished.
function after(asyncId) { }
// destroy() is called when the resource is destroyed.
function destroy(asyncId) { }
// promiseResolve() is called only for promise resources, when the
// resolve() function passed to the Promise constructor is invoked
// (either directly or through other means of resolving a promise).
function promiseResolve(asyncId) { }
async_hooks.createHook(callbacks)
Agregado en: v8.1.0
callbacks
<Object> Los Callbacks del Hook para registrarinit
<Function> Elcallback init
.before
<Function> Elcallback before
.after
<Function> Elcallback after
.destroy
<Function> Elcallback destroy
.promiseResolve
<Function> Elcallback promiseResolve
.
Devuelve: <AsyncHook> Instancia utilizada para deshabilitar y habilitar hooks
Registra funciones para ser llamadas para diferentes eventos del ciclo de vida de cada operación asíncrona.
Los callbacks init()
/before()
/after()
/destroy()
son llamados para el respectivo evento asíncrono durante el ciclo de vida de un recurso.
Todos los callbacks son opcionales. Por ejemplo, si solo es necesario rastrear la limpieza de recursos, entonces solo es necesario pasar el callback destroy
. Los detalles de todas las funciones que se pueden pasar a callbacks
están en la sección Callbacks del Hook.
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) { },
});
Los callbacks serán heredados a través de la cadena de prototipos:
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 asíncronos cuyo ciclo de vida se rastrea a través del mecanismo de async hooks, los callbacks init()
, before()
, after()
y destroy()
no deben ser funciones asíncronas que devuelvan promesas.
Manejo de errores
Si alguna devolución de llamada de AsyncHook
lanza un error, la aplicación imprimirá el seguimiento de la pila y se cerrará. La ruta de salida sigue la de una excepción no capturada, pero todos los listeners de 'uncaughtException'
se eliminan, lo que obliga al proceso a cerrarse. Las devoluciones de llamada de 'exit'
seguirán siendo llamadas a menos que la aplicación se ejecute con --abort-on-uncaught-exception
, en cuyo caso se imprimirá un seguimiento 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 en la vida útil de un objeto, por ejemplo, durante la construcción y destrucción de la clase. Debido a esto, se considera necesario cerrar el proceso rápidamente para evitar una interrupción involuntaria 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.
Imprimir 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
. El uso de console.log()
o operaciones asíncronas similares dentro de una función de devolución de llamada de AsyncHook
provocará 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.
import { writeFileSync } from 'node:fs';
import { format } from 'node:util';
function debug(...args) {
// Use a function like this one when debugging inside an AsyncHook callback
writeFileSync('log.out', `${format(...args)}\n`, { flag: 'a' });
}
const fs = require('node:fs');
const util = require('node:util');
function debug(...args) {
// Use a function like this one when debugging inside an AsyncHook callback
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 cuando fue el propio registro el que provocó que se llamara 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 del ciclo de vida de las operaciones asíncronas.
asyncHook.enable()
- Devuelve: <AsyncHook> Una referencia a
asyncHook
.
Habilita las devoluciones de llamada para una instancia dada de AsyncHook
. Si no se proporcionan devoluciones de llamada, habilitar es una operación nula.
La instancia de AsyncHook
está deshabilitada por defecto. Si la instancia de AsyncHook
debe habilitarse inmediatamente después de la creación, se puede utilizar el siguiente patrón.
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()
- Devuelve: <AsyncHook> Una referencia a
asyncHook
.
Deshabilita las devoluciones de llamada para una instancia dada de AsyncHook
del grupo global de devoluciones de llamada de AsyncHook
que se ejecutarán. Una vez que un hook ha sido deshabilitado, no se volverá a llamar hasta que se habilite.
Para la coherencia de la API, disable()
también devuelve la instancia de AsyncHook
.
Devoluciones de llamada del Hook
Los eventos clave en el ciclo de vida de los eventos asíncronos se han clasificado 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 del 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.
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 cada nuevo recurso se le asigna un ID que es único dentro del alcance 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
. Generalmente, 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 independientemente del propio Node.js.
También existe el tipo de recurso PROMISE
, que se utiliza para rastrear las instancias de Promise
y el trabajo asíncrono programado por ellas.
Los usuarios pueden definir su propio type
cuando utilizan la API pública de inserción.
Es posible que haya colisiones de nombres de tipo. Se anima a los integradores a utilizar prefijos únicos, como el nombre del paquete npm, para evitar colisiones al escuchar los hooks.
triggerAsyncId
triggerAsyncId
es el asyncId
del recurso que provocó (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
:
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);
Salida al golpear el servidor con nc localhost 8080
:
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 ninguna pila de JavaScript por encima). Con solo esa información, sería imposible vincular los recursos en términos de lo que causó su creación, por lo que a triggerAsyncId
se le da 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 Node.js en sí mismos 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 del seguimiento del contexto está cubierto por la API estable AsyncLocalStorage
. Este ejemplo solo ilustra el funcionamiento de los hooks 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 función de retorno de llamada a listen()
. El formato de salida es ligeramente más elaborado para que el contexto de llamada sea más fácil de ver.
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);
});
Salida al solo iniciar el servidor:
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á delineado por las llamadas a before
y after
.
Usar solo execution
para graficar los resultados de la asignación de recursos da como resultado lo siguiente:
root(1)
^
|
TickObject(6)
^
|
Timeout(7)
El TCPSERVERWRAP
no es parte de este gráfico, aunque fue la razón por la que se llamó a console.log()
. Esto se debe a que vincularse a un puerto sin un nombre de host es una operación síncrona, pero para mantener una API completamente asíncrona, la función de retorno de llamada del usuario se coloca en un process.nextTick()
. Por eso TickObject
está presente en la salida y es un 'padre' para la función de retorno 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 se puede representar con el siguiente gráfico:
bootstrap(1)
|
˅
TCPSERVERWRAP(5)
|
˅
TickObject(6)
|
˅
Timeout(7)
before(asyncId)
asyncId
<number>
Cuando una operación asíncrona se inicia (como un servidor TCP recibiendo una nueva conexión) o se completa (como escribir datos en el disco) se llama a una función callback para notificar al usuario. La función callback before
se llama justo antes de que dicha función callback sea ejecutada. asyncId
es el identificador único asignado al recurso que está a punto de ejecutar la función callback.
La función callback before
será llamada de 0 a N veces. La función callback before
será llamada típicamente 0 veces si la operación asíncrona fue cancelada o, por ejemplo, si no se reciben conexiones por un servidor TCP. Los recursos asíncronos persistentes como un servidor TCP típicamente llamarán a la función callback before
múltiples veces, mientras que otras operaciones como fs.open()
la llamarán solo una vez.
after(asyncId)
asyncId
<number>
Llamada inmediatamente después de que la función callback especificada en before
se completa.
Si una excepción no capturada ocurre durante la ejecución de la función callback, entonces after
se ejecutará después de que el evento 'uncaughtException'
se emita o un controlador de domain
se ejecute.
destroy(asyncId)
asyncId
<number>
Llamada después de que el recurso correspondiente a asyncId
sea destruido. También es llamada asíncronamente desde la API de incrustación 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, entonces esto no será un problema.
El uso del hook destroy resulta en una sobrecarga adicional porque permite el seguimiento de las instancias de Promise
a través del recolector de basura.
promiseResolve(asyncId)
Agregado en: v8.6.0
asyncId
<number>
Llamada cuando la función resolve
pasada al constructor Promise
es invocada (ya sea directamente o a través de otros medios para resolver una promesa).
resolve()
no realiza ningún trabajo síncrono observable.
La Promise
no está necesariamente cumplida o rechazada en este punto si la Promise
fue resuelta asumiendo el estado de otra Promise
.
new Promise((resolve) => resolve(true)).then((a) => {});
llama a las siguientes funciones callback:
init para PROMISE con id 5, id del disparador: 1
resolución de promesa 5 # corresponde a resolve(true)
init para PROMISE con id 6, id del disparador: 5 # la Promise regresada por then()
before 6 # se ingresa a la función callback then()
resolución de promesa 6 # la función callback then() resuelve la promesa al regresar
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 controlador internos de Node.js con API no documentadas. El uso de cualquier función o propiedad en el objeto probablemente bloqueará su aplicación y debe evitarse.
Usar executionAsyncResource()
en el contexto de ejecución de nivel superior devolverá un objeto vacío ya que no hay ningún objeto de controlador o solicitud para usar, pero tener un objeto que represente el nivel superior puede ser útil.
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
});
Esto se puede usar para implementar el almacenamiento local de continuación sin el uso de un Map
de seguimiento para almacenar los metadatos:
import { createServer } from 'node:http';
import {
executionAsyncId,
executionAsyncResource,
createHook,
} from 'node:async_hooks';
const sym = Symbol('state'); // Símbolo privado para evitar la 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);
const { createServer } = require('node:http');
const {
executionAsyncId,
executionAsyncResource,
createHook,
} = require('node:async_hooks');
const sym = Symbol('state'); // Símbolo privado para evitar la 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()
[Historia]
Versión | Cambios |
---|---|
v8.2.0 | Se renombró de currentId . |
v8.1.0 | Añadido en: v8.1.0 |
- Devuelve: <number> El
asyncId
del contexto de ejecución actual. Útil para rastrear cuándo algo llama.
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()
});
El ID devuelto por executionAsyncId()
está relacionado con el tiempo de ejecución, no con la causalidad (que está cubierta por triggerAsyncId()
):
const server = net.createServer((conn) => {
// Devuelve el ID del servidor, no de la nueva conexión, porque el
// callback 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 todos los
// callbacks pasados a .listen() están envueltos en un nextTick().
async_hooks.executionAsyncId();
});
Es posible que los contextos de Promise no obtengan 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 al callback que se está ejecutando actualmente.
const server = net.createServer((conn) => {
// El recurso que causó (o desencadenó) la llamada a este callback
// 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 todos los callbacks pasados a .listen() están envueltos en un nextTick()
// el callback en sí existe porque se realizó la llamada a .listen() del servidor.
// Por lo tanto, el valor de retorno sería el ID del servidor.
async_hooks.triggerAsyncId();
});
Es posible que los contextos de Promise no obtengan triggerAsyncId
s 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 pueden ser emitidos por el evento
async_hooks.init()
.
Esta característica suprime el uso obsoleto de process.binding('async_wrap').Providers
. Ver: DEP0111
Seguimiento de la ejecución de promesas
De forma predeterminada, a las ejecuciones de promesas no se les asignan asyncId
s debido a la naturaleza relativamente costosa de la API de introspección de promesas proporcionada por V8. Esto significa que los programas que usan promesas o async
/ await
no obtendrán identificadores de ejecución y activación correctos para los contextos de devolución de llamada de promesas de forma predeterminada.
import { executionAsyncId, triggerAsyncId } from 'node:async_hooks';
Promise.resolve(1729).then(() => {
console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`);
});
// produces:
// eid 1 tid 0
const { executionAsyncId, triggerAsyncId } = require('node:async_hooks');
Promise.resolve(1729).then(() => {
console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`);
});
// produces:
// eid 1 tid 0
Observe que la devolución de llamada then()
afirma haberse ejecutado en el contexto del ámbito externo, aunque hubo un salto asíncrono involucrado. Además, el valor de 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()
.
La instalación de enlaces asíncronos a través de async_hooks.createHook
habilita el seguimiento de la ejecución de promesas:
import { createHook, executionAsyncId, triggerAsyncId } from 'node:async_hooks';
createHook({ init() {} }).enable(); // forces PromiseHooks to be enabled.
Promise.resolve(1729).then(() => {
console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`);
});
// produces:
// eid 7 tid 6
const { createHook, executionAsyncId, triggerAsyncId } = require('node:async_hooks');
createHook({ init() {} }).enable(); // forces PromiseHooks to be enabled.
Promise.resolve(1729).then(() => {
console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`);
});
// produces:
// eid 7 tid 6
En este ejemplo, agregar cualquier función de enlace real habilitó el seguimiento de 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()
, 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
se ejecutan solo en promesas encadenadas. Eso significa que las promesas no creadas por then()
/catch()
no tendrán las devoluciones de llamada before
y after
activadas en ellas. Para obtener más detalles, consulte los detalles de la API V8 PromiseHooks.
API de incrustación de JavaScript
Los desarrolladores de bibliotecas que gestionan sus propios recursos asíncronos realizando tareas como E/S, agrupación de conexiones o gestión de colas de devolución de llamada pueden utilizar la API de JavaScript AsyncResource
para que se llamen todas las devoluciones de llamada adecuadas.
Clase: AsyncResource
La documentación para esta clase se ha movido a AsyncResource
.
Clase: AsyncLocalStorage
La documentación para esta clase se ha movido a AsyncLocalStorage
.