Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

Async Hooks no Node.js

Atualizado em: 27 de setembro de 2026

Rack de servidores processando fluxos de dados no Node.js

Async Hooks é uma API do Node.js para acompanhar o ciclo de vida de recursos assíncronos. Ela permite observar quando timers, promises, sockets, requisições, operações de arquivo e outros recursos são criados, iniciados, concluídos e destruídos.

A API é poderosa, mas de baixo nível. Ela aparece na base de ferramentas de tracing, correlação de logs, diagnóstico e propagação de contexto. Para a maioria das aplicações, AsyncLocalStorage é a opção mais simples para armazenar contexto por requisição. Async Hooks é indicado quando você precisa entender ou construir a infraestrutura por trás dessa propagação.

Conceitos principais

Cada recurso assíncrono possui identificadores:

  • asyncId: identifica o recurso atual;
  • triggerAsyncId: identifica o recurso que causou sua criação;
  • executionAsyncId: identifica o contexto em execução;
  • executionAsyncResource: retorna o objeto associado ao contexto atual.

Esses vínculos formam uma árvore ou grafo de causalidade. Uma requisição HTTP pode criar promises, consultas, timers e chamadas externas; cada recurso carrega uma relação com o anterior.

Criando um hook

import asyncHooks from 'node:async_hooks';

const hook = asyncHooks.createHook({
  init(asyncId, type, triggerAsyncId, resource) {
    // recurso criado
  },
  before(asyncId) {
    // callback será executado
  },
  after(asyncId) {
    // callback terminou
  },
  destroy(asyncId) {
    // recurso destruído
  },
  promiseResolve(asyncId) {
    // promise resolvida
  },
});

hook.enable();

Você não precisa implementar todos os callbacks. Cada um adiciona trabalho, então registre apenas o necessário.

Por que console.log pode ser perigoso

Os callbacks de Async Hooks podem ser acionados por operações realizadas pelo próprio logger. Usar console.log dentro de init pode criar novos recursos assíncronos, gerar recursão e produzir resultados confusos.

Para diagnóstico de baixo nível, use escrita síncrona:

import { writeFileSync } from 'node:fs';
import asyncHooks from 'node:async_hooks';

function debug(message) {
  writeFileSync(1, `${message}\n`);
}

const hook = asyncHooks.createHook({
  init(asyncId, type, triggerAsyncId) {
    debug(`init ${asyncId} ${type} trigger=${triggerAsyncId}`);
  },
});

hook.enable();

O descritor 1 representa a saída padrão. Escrita síncrona também tem custo; use apenas em capturas controladas.

Observando um timer

import asyncHooks from 'node:async_hooks';
import { writeFileSync } from 'node:fs';

const hook = asyncHooks.createHook({
  init(asyncId, type, triggerAsyncId) {
    writeFileSync(1, `${type} id=${asyncId} trigger=${triggerAsyncId}\n`);
  },
  before(asyncId) {
    writeFileSync(1, `before ${asyncId}\n`);
  },
  after(asyncId) {
    writeFileSync(1, `after ${asyncId}\n`);
  },
  destroy(asyncId) {
    writeFileSync(1, `destroy ${asyncId}\n`);
  },
});

hook.enable();

setTimeout(() => {
  writeFileSync(1, 'timer executado\n');
}, 50);

O tipo costuma indicar a classe interna do recurso, como Timeout, PROMISE ou recursos de rede. Não dependa de todos os nomes internos como contrato público sem verificar a documentação e testar a versão usada.

executionAsyncId

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

console.log({
  execution: executionAsyncId(),
  trigger: triggerAsyncId(),
});

Esses valores são úteis durante diagnóstico, mas não devem ser usados como identificadores permanentes de negócio. Eles existem dentro da vida do processo.

Mapa de contexto manual

Uma implementação didática pode manter contexto por asyncId:

import asyncHooks from 'node:async_hooks';

const contexts = new Map();

const hook = asyncHooks.createHook({
  init(asyncId, type, triggerAsyncId) {
    if (contexts.has(triggerAsyncId)) {
      contexts.set(asyncId, contexts.get(triggerAsyncId));
    }
  },
  destroy(asyncId) {
    contexts.delete(asyncId);
  },
  promiseResolve(asyncId) {
    contexts.delete(asyncId);
  },
});

hook.enable();

export function setCurrentContext(value) {
  contexts.set(asyncHooks.executionAsyncId(), value);
}

export function getCurrentContext() {
  return contexts.get(asyncHooks.executionAsyncId());
}

Esse exemplo ajuda a entender a ideia, mas não cobre todas as sutilezas de recursos reutilizados, promises, destruição tardia e limites de memória. Para produção, prefira AsyncLocalStorage.

Criando um AsyncResource

Bibliotecas que implementam filas, pools ou callbacks próprios podem usar AsyncResource para preservar a relação assíncrona:

import { AsyncResource } from 'node:async_hooks';

class TaskResource extends AsyncResource {
  constructor() {
    super('TaskResource');
  }

  run(callback, value) {
    return this.runInAsyncScope(callback, null, value);
  }

  close() {
    this.emitDestroy();
  }
}

const resource = new TaskResource();
resource.run((value) => {
  console.log(value);
}, 'tarefa concluída');
resource.close();

runInAsyncScope executa o callback no contexto associado ao recurso. Isso permite que AsyncLocalStorage, traces e ferramentas de diagnóstico reconheçam a continuidade.

Fila de tarefas com contexto

import { AsyncResource } from 'node:async_hooks';

class Task {
  constructor(callback) {
    this.callback = callback;
    this.resource = new AsyncResource('TaskQueueItem');
  }

  execute() {
    try {
      this.resource.runInAsyncScope(this.callback);
    } finally {
      this.resource.emitDestroy();
    }
  }
}

const queue = [];

export function enqueue(callback) {
  queue.push(new Task(callback));
}

export function consume() {
  const task = queue.shift();
  task?.execute();
}

Sem AsyncResource, uma biblioteca pode quebrar a propagação esperada de contexto, especialmente quando armazena callbacks para executar depois.

Integração com tracing

Agentes de APM e bibliotecas de observabilidade usam mecanismos assíncronos para manter o span atual entre callbacks. Um fluxo típico:

  1. receber uma requisição;
  2. criar trace e span raiz;
  3. associar o contexto ao recurso atual;
  4. propagar para promises e I/O;
  5. criar spans filhos para banco e HTTP;
  6. encerrar o span raiz ao responder.

Implementar isso manualmente é difícil. OpenTelemetry oferece abstrações e instrumentações prontas, mas entender Async Hooks ajuda a diagnosticar falhas de contexto.

Promises

Promises geram muitos recursos. Ativar callbacks detalhados em uma aplicação intensa pode produzir grande volume. Filtre tipos e use amostragem.

const interestingTypes = new Set([
  'PROMISE',
  'Timeout',
  'TCPWRAP',
  'HTTPINCOMINGMESSAGE',
]);

const hook = asyncHooks.createHook({
  init(asyncId, type, triggerAsyncId) {
    if (!interestingTypes.has(type)) return;
    // registrar amostra
  },
});

Os tipos disponíveis podem variar. Primeiro faça uma captura curta para descobrir o comportamento da versão e da plataforma.

Overhead

Async Hooks pode adicionar custo significativo, principalmente quando:

  • todos os callbacks estão ativos;
  • cada evento gera log;
  • o mapa de contexto cresce;
  • há muitas promises pequenas;
  • o callback executa serialização;
  • a instrumentação não usa amostragem.

Faça benchmark com a instrumentação ativada e desativada. Meça throughput, percentis, CPU, memória e event loop delay.

Vazamentos no mapa

Um mapa por asyncId precisa remover entradas. Alguns recursos só recebem destroy quando o garbage collector atua ou quando determinados mecanismos estão habilitados. Confiar cegamente em um único callback pode reter contexto.

Use estruturas e APIs oficiais, imponha limites e monitore o tamanho do mapa.

Diagnóstico de recursos pendentes

Async Hooks pode ajudar a descobrir por que um processo não encerra. Registre recursos criados e não destruídos no fim de um teste.

const active = new Map();

const hook = asyncHooks.createHook({
  init(asyncId, type, triggerAsyncId) {
    active.set(asyncId, { type, triggerAsyncId });
  },
  destroy(asyncId) {
    active.delete(asyncId);
  },
  promiseResolve(asyncId) {
    active.delete(asyncId);
  },
});

Timers, servidores, sockets e handles internos podem aparecer. Interprete o resultado com conhecimento do framework e filtre recursos esperados.

Testes

Em testes automatizados, hooks globais podem interferir em outros casos. Habilite no início do teste específico e desabilite no final:

hook.enable();
try {
  await scenario();
} finally {
  hook.disable();
  active.clear();
}

Execute o cenário isoladamente para reduzir ruído do runner.

Worker Threads

Cada Worker Thread possui seu próprio ambiente de execução. Um hook criado na thread principal não observa automaticamente todos os eventos internos do worker. Instrumente cada worker e envie apenas dados agregados.

Segurança e privacidade

Evite armazenar tokens, conteúdo de requisições e dados pessoais no contexto propagado. Um contexto pequeno com request ID, trace ID e informações de operação é mais seguro e eficiente.

Quando não usar

Não use Async Hooks apenas para medir duração de função: perf_hooks é mais simples. Não use para logs correlacionados se AsyncLocalStorage atende. Não trate a API como tracing completo; você ainda precisa de propagação entre serviços, amostragem, exportação e semântica de spans.

Erros comuns

  • usar console.log dentro dos callbacks;
  • armazenar contexto sem limpeza;
  • ativar todos os eventos em produção;
  • depender de nomes internos sem teste;
  • usar asyncId como ID de negócio;
  • ignorar Worker Threads;
  • não chamar emitDestroy em AsyncResource próprio;
  • reinventar AsyncLocalStorage sem necessidade;
  • medir desempenho apenas com instrumentação desativada.

Fluxo recomendado

  1. defina qual relação assíncrona precisa observar;
  2. faça uma captura curta em desenvolvimento;
  3. filtre tipos relevantes;
  4. evite I/O assíncrono nos callbacks;
  5. meça overhead;
  6. limite retenção e volume;
  7. prefira AsyncResource para bibliotecas;
  8. prefira AsyncLocalStorage para contexto de aplicação.

Combine Async Hooks com perf_hooks, diagnóstico em Clinic.js, profiling em –prof e traces com OpenTelemetry no Node.js.

Consulte a documentação oficial de Async Hooks e a documentação oficial de contexto assíncrono.

10 melhores cursos de programação em 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita