O AsyncResource no Node.js permite representar manualmente uma operação assíncrona para que Async Hooks, AsyncLocalStorage e ferramentas de observabilidade mantenham a relação correta entre criação, execução e encerramento. Ele é útil em pools de tarefas, filas, callbacks nativos, bibliotecas e abstrações que executam trabalho fora do fluxo assíncrono reconhecido automaticamente pelo runtime.
Sem AsyncResource, um callback disparado por uma fila personalizada pode perder o contexto da requisição que o criou. Isso afeta IDs de correlação, tracing, logs e métricas. Ao executar o callback dentro do escopo do recurso, a aplicação restaura a causalidade esperada.
Neste guia, você aprenderá a criar recursos, usar runInAsyncScope(), trabalhar com AsyncLocalStorage, emitir destroy, criar pools, vincular funções, tratar erros, evitar vazamentos e testar propagação de contexto.
O que é AsyncResource?
AsyncResource é uma classe do módulo node:async_hooks. A documentação oficial de AsyncResource no Node.js descreve construtor, métodos e exemplos. O guia oficial de Async Hooks apresenta IDs e ciclo de vida.
Para contexto por requisição, consulte AsyncLocalStorage no Node.js. Para eventos do runtime, veja Async Hooks no Node.js. O artigo de Event Loop no Node.js ajuda a entender a execução dos callbacks.
Criando um recurso
const {
AsyncResource
} = require('node:async_hooks');
const resource = new AsyncResource('CUSTOM_TASK');O nome identifica o tipo nas ferramentas de diagnóstico. Use uma constante estável e específica.
runInAsyncScope()
resource.runInAsyncScope(
callback,
thisArg,
argument1,
argument2
);O método executa a função como se ela pertencesse ao contexto assíncrono do recurso.
Exemplo básico
function schedule(callback) {
const resource = new AsyncResource('SCHEDULED_CALLBACK');
setImmediate(() => {
try {
resource.runInAsyncScope(callback);
} finally {
resource.emitDestroy();
}
});
}Mesmo que setImmediate() já seja rastreado, o exemplo mostra como associar o callback ao recurso lógico criado no momento do agendamento.
AsyncLocalStorage
const {
AsyncLocalStorage
} = require('node:async_hooks');
const storage = new AsyncLocalStorage();
storage.run({ requestId: 'req-123' }, () => {
queue.add(() => {
console.log(storage.getStore());
});
});Uma fila mal implementada pode executar o callback em um contexto diferente. AsyncResource ajuda a preservar o contexto da chamada add().
Fila com contexto
class Task {
constructor(callback) {
this.callback = callback;
this.resource = new AsyncResource('TASK_QUEUE_ITEM');
}
run() {
return this.resource.runInAsyncScope(
this.callback,
null
);
}
destroy() {
this.resource.emitDestroy();
}
}Executando a fila
async function processTask(task) {
try {
return await task.run();
} finally {
task.destroy();
}
}O finally garante encerramento mesmo quando o callback falha.
Recurso por tarefa
Crie um AsyncResource para cada unidade lógica. Reutilizar um único recurso para tarefas de requisições diferentes mistura causalidade e contexto.
Pool de Worker Threads
Um pool pode criar um recurso para cada job enviado ao worker. Quando a resposta retorna, o callback é executado no escopo do job original.
class WorkerJob extends AsyncResource {
constructor(callback) {
super('WORKER_POOL_JOB');
this.callback = callback;
}
complete(error, value) {
try {
this.runInAsyncScope(
this.callback,
null,
error,
value
);
} finally {
this.emitDestroy();
}
}
}Veja Worker Threads no Node.js para pools e mensagens.
triggerAsyncId
O construtor pode receber opções:
const resource = new AsyncResource(
'CUSTOM_TASK',
{
triggerAsyncId: executionAsyncId(),
requireManualDestroy: true
}
);O triggerAsyncId indica qual recurso causou a criação. Normalmente o valor padrão já representa o contexto atual.
requireManualDestroy
Quando ativado, você assume responsabilidade por chamar emitDestroy(). Isso melhora precisão do ciclo de vida, mas esquecer a chamada prejudica ferramentas e pode manter metadados.
emitDestroy()
resource.emitDestroy();Chame uma vez quando o recurso não produzirá mais callbacks. Não emita destroy antes da conclusão.
asyncId()
console.log(resource.asyncId());O ID pode ajudar em diagnósticos, mas não deve ser usado como identificador persistente ou regra de autorização.
triggerAsyncId()
console.log(resource.triggerAsyncId());O valor conecta o recurso ao causador conhecido pelo runtime.
bind()
AsyncResource oferece formas de vincular uma função ao contexto:
const bound = resource.bind(callback);
setImmediate(bound);A disponibilidade e assinatura dependem da versão. Prefira um wrapper explícito quando precisa controlar destroy.
AsyncResource.bind()
A classe também pode oferecer método estático para vincular a função ao contexto atual:
const bound = AsyncResource.bind(callback);Teste com a versão mínima do projeto.
thisArg e argumentos
resource.runInAsyncScope(
handler,
service,
event
);O segundo argumento define this. Os demais são encaminhados à função.
Promises retornadas
runInAsyncScope() retorna o valor da função. Se ela retorna uma Promise, o chamador precisa aguardar e capturar rejeições.
await resource.runInAsyncScope(
async () => performTask()
);Destroy após Promise
try {
await resource.runInAsyncScope(callback);
} finally {
resource.emitDestroy();
}Não emita destroy imediatamente depois de obter a Promise sem aguardá-la, quando o recurso lógico inclui todo o trabalho assíncrono.
Erros síncronos
Erros lançados atravessam runInAsyncScope(). Use try/finally para limpeza e deixe a camada apropriada decidir como responder.
Callbacks múltiplos
Alguns recursos geram vários eventos, como uma assinatura. Mantenha o recurso vivo até cancelar a assinatura, então emita destroy.
EventEmitter personalizado
Um emissor que representa conexão persistente pode usar um AsyncResource por conexão, mas callbacks de eventos independentes podem exigir recursos próprios conforme a semântica.
Não envolva tudo
Promises, timers, filesystem, sockets e outras APIs nativas já são rastreadas. Use AsyncResource somente quando sua abstração quebra ou redefine a relação assíncrona.
Bibliotecas
Bibliotecas de pools, schedulers e bridges nativas devem considerar AsyncResource para interoperar com ferramentas de contexto sem exigir configuração do usuário.
Node-API
Addons nativos que chamam JavaScript de threads externas podem precisar criar escopo assíncrono por meio de APIs equivalentes. Consulte Node-API no Node.js quando disponível.
Observabilidade
AsyncResource melhora:
- IDs de correlação;
- traces;
- logs por requisição;
- diagnóstico com Async Hooks;
- atribuição de tarefas em pools;
- propagação de contexto.
OpenTelemetry
Instrumentações de tracing dependem de contexto assíncrono correto. Veja OpenTelemetry no Node.js.
Performance
Criar recursos e executar hooks possui custo. Meça em filas de alto volume. Use nomes de tipo estáveis e não ative hooks detalhados sem necessidade.
Vazamentos
Maps de tarefas, listeners e recursos nunca destruídos podem crescer. O problema não é apenas o AsyncResource; toda estrutura associada ao job precisa ser removida.
Timeouts
Uma tarefa que nunca completa deve expirar:
const timer = setTimeout(() => {
job.complete(new Error('Timeout'));
}, 5000);
job.onComplete = () => clearTimeout(timer);Garanta que complete seja idempotente.
Cancelamento
Associe um AbortSignal ao job e finalize o recurso quando o cancelamento for confirmado. Veja AbortController no Node.js.
Testando contexto
test('preserva requestId na fila', async () => {
await storage.run(
{ requestId: 'test-1' },
async () => {
const value = await queue.execute(() => {
return storage.getStore().requestId;
});
assert.equal(value, 'test-1');
}
);
});Testando erros
Verifique se o recurso é destruído, o job é removido da fila e a Promise é rejeitada quando o callback lança erro.
Testando concorrência
Execute várias tarefas com requestIds diferentes e confirme que os contextos não se misturam.
Erros comuns
- Um recurso para todas as tarefas: contextos se misturam.
- Esquecer emitDestroy: o ciclo fica incompleto.
- Destroy cedo demais: operações posteriores perdem relação.
- Não aguardar Promise: limpeza acontece antes da conclusão.
- Envolver APIs nativas sem necessidade: overhead aumenta.
- Sem timeout: jobs pendentes acumulam.
- Usar asyncId como ID de negócio: o valor não é persistente.
Boas práticas
- Crie um recurso por operação lógica.
- Use nome estável.
- Execute callbacks com runInAsyncScope.
- Emita destroy no momento correto.
- Use try/finally.
- Aguarde Promises.
- Limite filas.
- Aplique timeouts.
- Teste contextos concorrentes.
- Meça overhead.
Conclusão
O AsyncResource no Node.js permite que abstrações assíncronas personalizadas mantenham causalidade e contexto compatíveis com Async Hooks e AsyncLocalStorage.
Ele é especialmente valioso em pools, filas e bridges nativas. O uso correto exige um recurso por tarefa, execução dentro do escopo, destroy no momento adequado e limites para jobs pendentes. Com essas regras, logs e traces continuam ligados à requisição certa mesmo quando o trabalho passa por uma abstração personalizada.




