Skip to content

Ganchos Assíncronos

::: aviso [Estável: 1 - Experimental] Estável: 1 Estabilidade: 1 - Experimental. Por favor, migre para longe desta API, se puder. Não recomendamos o uso das APIs createHook, AsyncHook e executionAsyncResource, pois elas têm problemas de usabilidade, riscos de segurança e implicações de desempenho. Os casos de uso de rastreamento de contexto assíncrono são melhor atendidos pela API estável AsyncLocalStorage. Se você tiver um caso de uso para createHook, AsyncHook ou executionAsyncResource além da necessidade de rastreamento de contexto resolvida por AsyncLocalStorage ou dados de diagnóstico atualmente fornecidos pelo Canal de Diagnóstico, por favor, abra um problema em https://github.com/nodejs/node/issues descrevendo seu caso de uso para que possamos criar uma API mais focada em propósito. :::

Código-fonte: lib/async_hooks.js

Desencorajamos fortemente o uso da API async_hooks. Outras APIs que podem cobrir a maioria de seus casos de uso incluem:

O módulo node:async_hooks fornece uma API para rastrear recursos assíncronos. Ele pode ser acessado usando:

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

Terminologia

Um recurso assíncrono representa um objeto com um callback associado. Esse callback pode ser chamado várias vezes, como o evento 'connection' em net.createServer(), ou apenas uma única vez como em fs.open(). Um recurso também pode ser fechado antes que o callback seja chamado. AsyncHook não distingue explicitamente entre esses diferentes casos, mas os representará como o conceito abstrato que é um recurso.

Se Workers forem usados, cada thread tem uma interface async_hooks independente, e cada thread usará um novo conjunto de IDs assíncronos.

Visão geral

A seguir, uma visão geral simples da API pública.

js
import async_hooks from 'node:async_hooks'

// Retorna o ID do contexto de execução atual.
const eid = async_hooks.executionAsyncId()

// Retorna o ID do manipulador responsável por acionar o retorno de chamada do
// escopo de execução atual para chamar.
const tid = async_hooks.triggerAsyncId()

// Cria uma nova instância de AsyncHook. Todos esses retornos de chamada são opcionais.
const asyncHook = async_hooks.createHook({ init, before, after, destroy, promiseResolve })

// Permite que os retornos de chamada desta instância de AsyncHook sejam chamados. Esta não é uma ação implícita
// após executar o construtor, e deve ser executada explicitamente para começar
// a executar retornos de chamada.
asyncHook.enable()

// Desativa a escuta de novos eventos assíncronos.
asyncHook.disable()

//
// Os seguintes são os retornos de chamada que podem ser passados para createHook().
//

// init() é chamado durante a construção do objeto. O recurso pode não ter
// concluído a construção quando este retorno de chamada é executado. Portanto, todos os campos do
// recurso referenciado por "asyncId" podem não ter sido preenchidos.
function init(asyncId, type, triggerAsyncId, resource) {}

// before() é chamado imediatamente antes do retorno de chamada do recurso ser chamado. Ele pode ser
// chamado 0-N vezes para manipuladores (como TCPWrap) e será chamado exatamente 1
// vez para solicitações (como FSReqCallback).
function before(asyncId) {}

// after() é chamado imediatamente após o retorno de chamada do recurso ter terminado.
function after(asyncId) {}

// destroy() é chamado quando o recurso é destruído.
function destroy(asyncId) {}

// promiseResolve() é chamado apenas para recursos de promessa, quando a
// função resolve() passada para o construtor Promise é invocada
// (diretamente ou por outros meios de resolução de uma promessa).
function promiseResolve(asyncId) {}
js
const async_hooks = require('node:async_hooks')

// Retorna o ID do contexto de execução atual.
const eid = async_hooks.executionAsyncId()

// Retorna o ID do manipulador responsável por acionar o retorno de chamada do
// escopo de execução atual para chamar.
const tid = async_hooks.triggerAsyncId()

// Cria uma nova instância de AsyncHook. Todos esses retornos de chamada são opcionais.
const asyncHook = async_hooks.createHook({ init, before, after, destroy, promiseResolve })

// Permite que os retornos de chamada desta instância de AsyncHook sejam chamados. Esta não é uma ação implícita
// após executar o construtor, e deve ser executada explicitamente para começar
// a executar retornos de chamada.
asyncHook.enable()

// Desativa a escuta de novos eventos assíncronos.
asyncHook.disable()

//
// Os seguintes são os retornos de chamada que podem ser passados para createHook().
//

// init() é chamado durante a construção do objeto. O recurso pode não ter
// concluído a construção quando este retorno de chamada é executado. Portanto, todos os campos do
// recurso referenciado por "asyncId" podem não ter sido preenchidos.
function init(asyncId, type, triggerAsyncId, resource) {}

// before() é chamado imediatamente antes do retorno de chamada do recurso ser chamado. Ele pode ser
// chamado 0-N vezes para manipuladores (como TCPWrap) e será chamado exatamente 1
// vez para solicitações (como FSReqCallback).
function before(asyncId) {}

// after() é chamado imediatamente após o retorno de chamada do recurso ter terminado.
function after(asyncId) {}

// destroy() é chamado quando o recurso é destruído.
function destroy(asyncId) {}

// promiseResolve() é chamado apenas para recursos de promessa, quando a
// função resolve() passada para o construtor Promise é invocada
// (diretamente ou por outros meios de resolução de uma promessa).
function promiseResolve(asyncId) {}

async_hooks.createHook(callbacks)

Adicionado em: v8.1.0

Registra funções a serem chamadas para diferentes eventos de ciclo de vida de cada operação assíncrona.

Os callbacks init()/before()/after()/destroy() são chamados para o respectivo evento assíncrono durante o ciclo de vida de um recurso.

Todos os callbacks são opcionais. Por exemplo, se apenas a limpeza de recursos precisar ser rastreada, então apenas o callback destroy precisa ser passado. Os detalhes de todas as funções que podem ser passadas para callbacks estão na seção Callbacks de Hook.

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) {},
})

Os callbacks serão herdados por meio da cadeia de protótipos:

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

Como as promises são recursos assíncronos cujo ciclo de vida é rastreado através do mecanismo de async hooks, os callbacks init(), before(), after() e destroy() não devem ser funções assíncronas que retornam promises.

Tratamento de erros

Se alguma das funções de retorno de chamada AsyncHook lançar um erro, a aplicação imprimirá o rastreamento da pilha e sairá. O caminho de saída segue o de uma exceção não capturada, mas todos os ouvintes de 'uncaughtException' são removidos, forçando assim o processo a sair. As funções de retorno de chamada de 'exit' ainda serão chamadas, a menos que a aplicação seja executada com --abort-on-uncaught-exception, caso em que um rastreamento de pilha será impresso e a aplicação sairá, deixando um arquivo core.

A razão para esse comportamento de tratamento de erros é que essas funções de retorno de chamada estão sendo executadas em pontos potencialmente voláteis na vida útil de um objeto, por exemplo, durante a construção e destruição da classe. Por causa disso, considera-se necessário derrubar o processo rapidamente para evitar uma interrupção não intencional no futuro. Isso está sujeito a alterações no futuro, caso uma análise abrangente seja realizada para garantir que uma exceção possa seguir o fluxo de controle normal sem efeitos colaterais não intencionais.

Imprimindo em funções de retorno de chamada AsyncHook

Como a impressão no console é uma operação assíncrona, console.log() fará com que as funções de retorno de chamada AsyncHook sejam chamadas. O uso de console.log() ou operações assíncronas semelhantes dentro de uma função de retorno de chamada AsyncHook causará uma recursão infinita. Uma solução fácil para isso ao depurar é usar uma operação de log síncrona como fs.writeFileSync(file, msg, flag). Isso imprimirá no arquivo e não invocará AsyncHook recursivamente porque é síncrono.

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

function debug(...args) {
  // Use uma função como esta ao depurar dentro de uma função de retorno de chamada AsyncHook
  writeFileSync('log.out', `${format(...args)}\n`, { flag: 'a' })
}
js
const fs = require('node:fs')
const util = require('node:util')

function debug(...args) {
  // Use uma função como esta ao depurar dentro de uma função de retorno de chamada AsyncHook
  fs.writeFileSync('log.out', `${util.format(...args)}\n`, { flag: 'a' })
}

Se uma operação assíncrona for necessária para o registro, é possível rastrear o que causou a operação assíncrona usando as informações fornecidas pelo próprio AsyncHook. O registro deve ser ignorado quando foi o próprio registro que fez com que a função de retorno de chamada AsyncHook fosse chamada. Ao fazer isso, a recursão infinita é interrompida.

Classe: AsyncHook

A classe AsyncHook expõe uma interface para rastrear eventos do ciclo de vida de operações assíncronas.

asyncHook.enable()

Habilita os callbacks para uma dada instância de AsyncHook. Se nenhum callback for fornecido, habilitar é uma operação nula.

A instância de AsyncHook é desabilitada por padrão. Se a instância de AsyncHook deve ser habilitada imediatamente após a criação, o seguinte padrão pode ser usado.

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

Desabilita os callbacks para uma dada instância de AsyncHook do pool global de callbacks de AsyncHook a serem executados. Uma vez que um hook foi desabilitado, ele não será chamado novamente até ser habilitado.

Para consistência da API, disable() também retorna a instância de AsyncHook.

Callbacks de Hook

Eventos chave no ciclo de vida de eventos assíncronos foram categorizados em quatro áreas: instanciação, antes/depois que o callback é chamado e quando a instância é destruída.

init(asyncId, type, triggerAsyncId, resource)

  • asyncId <number> Um ID único para o recurso assíncrono.
  • type <string> O tipo de recurso assíncrono.
  • triggerAsyncId <number> O ID único do recurso assíncrono no contexto de execução em que este recurso assíncrono foi criado.
  • resource <Object> Referência para o recurso que representa a operação assíncrona, precisa ser liberado durante o destroy.

Chamado quando uma classe é construída que tem a possibilidade de emitir um evento assíncrono. Isso não significa que a instância deve chamar before/after antes de destroy ser chamado, apenas que a possibilidade existe.

Este comportamento pode ser observado fazendo algo como abrir um recurso e depois fechá-lo antes que o recurso possa ser usado. O seguinte trecho demonstra isso.

js
import { createServer } from 'node:net'

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

Todo novo recurso recebe um ID que é único dentro do escopo da instância atual do Node.js.

type

O type é uma string que identifica o tipo de recurso que fez com que init fosse chamado. Geralmente, ele corresponderá ao nome do construtor do recurso.

O type de recursos criados pelo próprio Node.js pode mudar em qualquer lançamento do Node.js. Os valores válidos incluem TLSWRAP, TCPWRAP, TCPSERVERWRAP, GETADDRINFOREQWRAP, FSREQCALLBACK, Microtask e Timeout. Inspecione o código-fonte da versão do Node.js usada para obter a lista completa.

Além disso, os usuários de AsyncResource criam recursos assíncronos independentes do próprio Node.js.

Existe também o tipo de recurso PROMISE, que é usado para rastrear instâncias de Promise e trabalho assíncrono agendado por elas.

Os usuários podem definir seu próprio type ao usar a API pública do embedder.

É possível ter colisões de nomes de tipo. Os embedders são incentivados a usar prefixos exclusivos, como o nome do pacote npm, para evitar colisões ao ouvir os hooks.

triggerAsyncId

triggerAsyncId é o asyncId do recurso que causou (ou "acionou") a inicialização do novo recurso e que fez com que init fosse chamado. Isso é diferente de async_hooks.executionAsyncId() que mostra apenas quando um recurso foi criado, enquanto triggerAsyncId mostra por que um recurso foi criado.

A seguir está uma demonstração simples 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)

Saída ao atingir o servidor com nc localhost 8080:

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

O TCPSERVERWRAP é o servidor que recebe as conexões.

O TCPWRAP é a nova conexão do cliente. Quando uma nova conexão é feita, a instância TCPWrap é construída imediatamente. Isso acontece fora de qualquer pilha JavaScript. (Um executionAsyncId() de 0 significa que está sendo executado a partir de C++ sem nenhuma pilha JavaScript acima dele.) Com apenas essa informação, seria impossível vincular recursos em termos do que causou a criação deles, então triggerAsyncId recebe a tarefa de propagar qual recurso é responsável pela existência do novo recurso.

resource

resource é um objeto que representa o recurso assíncrono real que foi inicializado. A API para acessar o objeto pode ser especificada pelo criador do recurso. Recursos criados pelo próprio Node.js são internos e podem mudar a qualquer momento. Portanto, nenhuma API é especificada para eles.

Em alguns casos, o objeto de recurso é reutilizado por razões de desempenho, portanto, não é seguro usá-lo como uma chave em um WeakMap ou adicionar propriedades a ele.

Exemplo de contexto assíncrono

O caso de uso de rastreamento de contexto é coberto pela API estável AsyncLocalStorage. Este exemplo ilustra apenas a operação de hooks assíncronos, mas AsyncLocalStorage se encaixa melhor neste caso de uso.

O seguinte é um exemplo com informações adicionais sobre as chamadas para init entre as chamadas before e after, especificamente como será o callback para listen(). A formatação da saída é um pouco mais elaborada para facilitar a visualização do contexto da chamada.

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, () => {
    // Vamos esperar 10ms antes de registrar que o servidor foi iniciado.
    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, () => {
    // Vamos esperar 10ms antes de registrar que o servidor foi iniciado.
    setTimeout(() => {
      console.log('>>>', async_hooks.executionAsyncId())
    }, 10)
  })

Saída apenas ao iniciar o 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 ilustrado no exemplo, executionAsyncId() e execution especificam o valor do contexto de execução atual; que é delineado por chamadas para before e after.

Usar apenas execution para representar graficamente a alocação de recursos resulta no seguinte:

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

O TCPSERVERWRAP não faz parte deste gráfico, mesmo que tenha sido a razão para o console.log() ser chamado. Isso ocorre porque a vinculação a uma porta sem um nome de host é uma operação síncrona, mas para manter uma API completamente assíncrona, o callback do usuário é colocado em um process.nextTick(). É por isso que TickObject está presente na saída e é um 'pai' para o callback de .listen().

O gráfico mostra apenas quando um recurso foi criado, não por quê, então para rastrear o por quê use triggerAsyncId. O que pode ser representado com o seguinte gráfico:

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

before(asyncId)

Quando uma operação assíncrona é iniciada (como um servidor TCP recebendo uma nova conexão) ou concluída (como escrever dados no disco), um callback é chamado para notificar o usuário. O callback before é chamado imediatamente antes da execução do dito callback. asyncId é o identificador único atribuído ao recurso que está prestes a executar o callback.

O callback before será chamado de 0 a N vezes. O callback before normalmente será chamado 0 vezes se a operação assíncrona foi cancelada ou, por exemplo, se nenhuma conexão for recebida por um servidor TCP. Recursos assíncronos persistentes como um servidor TCP normalmente chamarão o callback before várias vezes, enquanto outras operações como fs.open() o chamarão apenas uma vez.

after(asyncId)

Chamado imediatamente após a conclusão do callback especificado em before.

Se uma exceção não capturada ocorrer durante a execução do callback, então after será executado após o evento 'uncaughtException' ser emitido ou o handler de um domain ser executado.

destroy(asyncId)

Chamado depois que o recurso correspondente a asyncId é destruído. Ele também é chamado de forma assíncrona a partir da API do incorporador emitDestroy().

Alguns recursos dependem da coleta de lixo para limpeza, portanto, se uma referência for feita ao objeto resource passado para init, é possível que destroy nunca seja chamado, causando um vazamento de memória no aplicativo. Se o recurso não depender da coleta de lixo, isso não será um problema.

Usar o hook destroy resulta em uma sobrecarga adicional porque permite o rastreamento de instâncias de Promise por meio do coletor de lixo.

promiseResolve(asyncId)

Adicionado em: v8.6.0

Chamado quando a função resolve passada para o construtor Promise é invocada (diretamente ou por outros meios de resolver uma promise).

resolve() não realiza nenhum trabalho síncrono observável.

A Promise não é necessariamente cumprida ou rejeitada neste ponto se a Promise foi resolvida assumindo o estado de outra Promise.

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

chama os seguintes callbacks:

text
init para PROMISE com id 5, id de acionamento: 1
  resolução de promise 5      # corresponde a resolve(true)
init para PROMISE com id 6, id de acionamento: 5  # a Promise retornada por then()
  before 6               # o callback then() é inserido
  resolução de promise 6      # o callback then() resolve a promise retornando
  after 6

async_hooks.executionAsyncResource()

Adicionado em: v13.9.0, v12.17.0

  • Retorna: <Objeto> O recurso que representa a execução atual. Útil para armazenar dados dentro do recurso.

Objetos de recurso retornados por executionAsyncResource() são, na maioria das vezes, objetos de manipulação interna do Node.js com APIs não documentadas. Usar quaisquer funções ou propriedades no objeto provavelmente irá travar sua aplicação e deve ser evitado.

Usar executionAsyncResource() no contexto de execução de nível superior retornará um objeto vazio, pois não há nenhum objeto de manipulação ou requisição para usar, mas ter um objeto representando o nível superior pode 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
})

Isso pode ser usado para implementar armazenamento local de continuação sem o uso de um Map de rastreamento para armazenar os metadados:

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

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 poluição

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

[Histórico]

VersãoMudanças
v8.2.0Renomeado de currentId.
v8.1.0Adicionado em: v8.1.0
  • Retorna: <number> O asyncId do contexto de execução atual. Útil para rastrear quando algo chama.
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()
})

O ID retornado de executionAsyncId() está relacionado ao tempo de execução, não à causalidade (que é coberta por triggerAsyncId()):

js
const server = net
  .createServer(conn => {
    // Retorna o ID do servidor, não da nova conexão, porque o
    // callback é executado no escopo de execução do MakeCallback() do servidor.
    async_hooks.executionAsyncId()
  })
  .listen(port, () => {
    // Retorna o ID de um TickObject (process.nextTick()) porque todos os
    // callbacks passados para .listen() são encapsulados em um nextTick().
    async_hooks.executionAsyncId()
  })

Contextos de Promise podem não obter executionAsyncIds precisos por padrão. Consulte a seção sobre rastreamento de execução de promise.

async_hooks.triggerAsyncId()

  • Retorna: <number> O ID do recurso responsável por chamar o callback que está sendo executado no momento.
js
const server = net
  .createServer(conn => {
    // O recurso que causou (ou acionou) a chamada deste callback
    // foi o da nova conexão. Assim, o valor de retorno de triggerAsyncId()
    // é o asyncId de "conn".
    async_hooks.triggerAsyncId()
  })
  .listen(port, () => {
    // Mesmo que todos os callbacks passados para .listen() sejam encapsulados em um nextTick()
    // o callback em si existe porque a chamada para .listen() do servidor
    // foi feita. Portanto, o valor de retorno seria o ID do servidor.
    async_hooks.triggerAsyncId()
  })

Contextos de Promise podem não obter triggerAsyncIds válidos por padrão. Consulte a seção sobre rastreamento de execução de promise.

async_hooks.asyncWrapProviders

Adicionado em: v17.2.0, v16.14.0

  • Retorna: Um mapa de tipos de provedores para o ID numérico correspondente. Este mapa contém todos os tipos de eventos que podem ser emitidos pelo evento async_hooks.init().

Este recurso suprime o uso obsoleto de process.binding('async_wrap').Providers. Veja: DEP0111

Rastreamento da execução de Promises

Por padrão, as execuções de promises não recebem asyncIds devido à natureza relativamente cara da API de introspecção de promises fornecida pelo V8. Isso significa que os programas que usam promises ou async/await não obterão IDs de execução e gatilho corretos para contextos de callback de promises por padrão.

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

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

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

Observe que o callback then() afirma ter sido executado no contexto do escopo externo, mesmo que tenha havido um salto assíncrono envolvido. Além disso, o valor triggerAsyncId é 0, o que significa que estamos perdendo o contexto sobre o recurso que causou (acionou) a execução do callback then().

Instalar hooks assíncronos via async_hooks.createHook permite o rastreamento da execução de promises:

js
import { createHook, executionAsyncId, triggerAsyncId } from 'node:async_hooks'
createHook({ init() {} }).enable() // força a ativação do PromiseHooks.
Promise.resolve(1729).then(() => {
  console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`)
})
// produz:
// eid 7 tid 6
js
const { createHook, executionAsyncId, triggerAsyncId } = require('node:async_hooks')

createHook({ init() {} }).enable() // força a ativação do PromiseHooks.
Promise.resolve(1729).then(() => {
  console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`)
})
// produz:
// eid 7 tid 6

Neste exemplo, adicionar qualquer função de hook real habilitou o rastreamento de promises. Existem duas promises no exemplo acima; a promise criada por Promise.resolve() e a promise retornada pela chamada para then(). No exemplo acima, a primeira promise obteve o asyncId 6 e a última obteve asyncId 7. Durante a execução do callback then(), estamos executando no contexto da promise com asyncId 7. Esta promise foi acionada pelo recurso assíncrono 6.

Outra sutileza com promises é que os callbacks before e after são executados apenas em promises encadeadas. Isso significa que promises não criadas por then()/catch() não terão os callbacks before e after disparados nelas. Para mais detalhes, veja os detalhes da API PromiseHooks do V8.

API de incorporação JavaScript

Desenvolvedores de bibliotecas que lidam com seus próprios recursos assíncronos realizando tarefas como E/S, agrupamento de conexões ou gerenciamento de filas de retorno de chamada podem usar a API JavaScript AsyncResource para que todos os retornos de chamada apropriados sejam chamados.

Classe: AsyncResource

A documentação para esta classe foi movida para AsyncResource.

Classe: AsyncLocalStorage

A documentação para esta classe foi movida para AsyncLocalStorage.