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

Timers no Node.js: Guia Prático

Atualizado em: 8 de agosto de 2026

Terminal Linux usado em desenvolvimento e administração de aplicações Node.js

Temporizadores fazem parte de praticamente toda aplicação de servidor. Eles controlam atrasos, intervalos, timeouts, tentativas, tarefas periódicas e operações que devem continuar apenas depois de uma determinada etapa do ciclo de eventos. O módulo de Timers no Node.js fornece APIs simples, mas o comportamento real depende do event loop, da carga do processo e da forma como callbacks assíncronos são organizados.

Um timer não garante que o código será executado exatamente no instante informado. Ele define o menor atraso antes que o callback possa entrar na fila correspondente. Se a thread principal estiver ocupada, a execução ocorrerá mais tarde. Entender essa diferença é essencial para evitar cronômetros imprecisos, filas de callbacks e serviços que não encerram corretamente.

Neste guia, você aprenderá a usar setTimeout(), setInterval(), setImmediate(), as versões baseadas em Promises, cancelamento com AbortSignal, ref(), unref(), medição de atrasos e padrões seguros para produção.

Como os timers funcionam?

O Node.js executa JavaScript em uma thread principal e coordena operações assíncronas através do event loop. Quando você registra um timer, o runtime armazena o callback e o atraso mínimo. Depois que esse prazo termina, o callback pode ser processado na fase apropriada, desde que o event loop esteja livre.

A documentação oficial de Timers descreve as funções globais, os objetos retornados e a API de Promises. Para revisar a plataforma, consulte o que é Node.js e o que é JavaScript.

Usando setTimeout()

setTimeout() agenda uma função para depois de um atraso mínimo:

const timeout = setTimeout(() => {
  console.log('Executado depois de aproximadamente 2 segundos');
}, 2000);

O valor retornado é um objeto Timeout. Ele pode ser usado para cancelar, atualizar ou alterar a influência do timer no ciclo de vida do processo.

Cancelando um timeout

const timeout = setTimeout(() => {
  console.log('Esta mensagem não será exibida');
}, 5000);

clearTimeout(timeout);

Guarde a referência quando a operação puder ser concluída antes do prazo. Cancelar timers desnecessários reduz callbacks acumulados e evita efeitos depois que uma requisição já terminou.

Criando um intervalo

setInterval() repete o callback enquanto não for cancelado:

let executions = 0;

const interval = setInterval(() => {
  executions += 1;
  console.log('Execução', executions);

  if (executions === 5) {
    clearInterval(interval);
  }
}, 1000);

Intervalos não são ideais quando a tarefa pode demorar mais do que o período. Uma nova execução pode ficar pendente enquanto a anterior ainda está processando banco de dados, rede ou arquivos.

Intervalo seguro com timeout recursivo

Para evitar sobreposição, agende a próxima execução somente depois que a atual terminar:

async function runPeriodicTask() {
  try {
    await synchronizeData();
  } catch (error) {
    console.error('Falha na sincronização', error);
  } finally {
    setTimeout(runPeriodicTask, 5000);
  }
}

runPeriodicTask();

Esse padrão cria uma pausa real entre as execuções. Em produção, acrescente cancelamento e limite de falhas. O artigo sobre retry com backoff no Node.js apresenta estratégias para tentativas controladas.

setImmediate() e o event loop

setImmediate() agenda um callback para a fase check do event loop. Ele é útil para dividir um trabalho extenso e devolver o controle ao runtime:

setImmediate(() => {
  console.log('Executado em uma próxima iteração do event loop');
});

A ordem entre setTimeout(fn, 0) e setImmediate(fn) pode variar dependendo do contexto. Dentro de certos callbacks de I/O, setImmediate() costuma ser processado antes do timer de atraso zero. Não baseie uma regra de negócio em uma disputa entre essas duas filas.

queueMicrotask e process.nextTick

Microtarefas não são timers, mas aparecem em comparações de ordem:

console.log('início');

setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
queueMicrotask(() => console.log('microtask'));
process.nextTick(() => console.log('nextTick'));

console.log('fim');

Callbacks de process.nextTick() e microtarefas são processados antes de o event loop avançar para outras fases. Um loop que adiciona novas tarefas continuamente pode impedir timers e I/O de progredirem. Use essas APIs para pequenas continuações, não para processamento ilimitado.

Timers baseados em Promises

O módulo node:timers/promises facilita o uso com async e await:

const { setTimeout: delay } = require('node:timers/promises');

async function main() {
  await delay(1000);
  console.log('Um segundo depois');
}

main();

Renomear a função para delay evita confusão com a versão global baseada em callback.

Cancelamento com AbortController

const { setTimeout: delay } = require('node:timers/promises');

const controller = new AbortController();

setTimeout(() => controller.abort(), 500);

try {
  await delay(5000, null, {
    signal: controller.signal
  });
} catch (error) {
  if (error.name === 'AbortError') {
    console.log('Espera cancelada');
  }
}

O cancelamento impede que uma operação continue apenas porque um atraso foi iniciado. Veja também o guia de AbortController no Node.js.

Usando ref() e unref()

Por padrão, um timer ativo mantém o processo Node.js em execução. Com unref(), o timer deixa de impedir o encerramento:

const cleanupTimer = setInterval(() => {
  removeExpiredEntries();
}, 60000);

cleanupTimer.unref();

Se não houver outro trabalho ativo, o processo pode terminar sem esperar esse intervalo. ref() restaura o comportamento padrão. Esse recurso é útil para telemetria, limpeza opcional e tarefas auxiliares, mas não deve ser aplicado a uma operação obrigatória.

Atualizando um timeout com refresh()

O método refresh() reinicia a contagem sem criar outro objeto:

const inactivity = setTimeout(disconnectClient, 30000);

function activityDetected() {
  inactivity.refresh();
}

Esse padrão pode controlar inatividade em conexões. Ao encerrar o cliente, cancele o timeout para não manter referências desnecessárias.

Timers não são relógios exatos

Uma tarefa síncrona longa atrasa todos os callbacks:

const startedAt = Date.now();

setTimeout(() => {
  console.log('Atraso real:', Date.now() - startedAt);
}, 100);

const end = Date.now() + 1000;
while (Date.now() < end) {
  // Bloqueia a thread principal
}

O callback solicitado para 100 milissegundos executará somente depois que o loop síncrono terminar. Para cálculos pesados, use processos ou Worker Threads, em vez de esperar que timers resolvam o bloqueio.

Medindo event loop lag

Uma forma simples de observar atrasos é comparar o horário esperado com o horário real:

const intervalMs = 1000;
let expected = Date.now() + intervalMs;

setInterval(() => {
  const now = Date.now();
  const lag = now - expected;

  console.log({ lag });
  expected = now + intervalMs;
}, intervalMs);

Em sistemas reais, utilize métricas do runtime e monitore percentis. Picos frequentes podem indicar CPU saturada, callbacks síncronos, garbage collection ou excesso de tarefas.

Timeouts em chamadas externas

Não confunda um timer isolado com o cancelamento da operação. Este padrão é incompleto:

await Promise.race([
  fetch(url),
  delay(5000).then(() => {
    throw new Error('Timeout');
  })
]);

A Promise rejeita, mas a requisição pode continuar consumindo recursos. Prefira enviar um AbortSignal para a operação. Também cancele o timer quando a chamada terminar, evitando trabalho residual.

Agendamento persistente

Timers existem apenas na memória do processo. Se o servidor reiniciar, o agendamento desaparece. Para tarefas importantes, use uma fila persistente, um agendador externo ou cron do ambiente. O conteúdo sobre BullMQ no Node.js explica jobs armazenados no Redis.

Encerramento da aplicação

Durante o shutdown, pare intervalos, cancele timeouts e aguarde tarefas críticas:

let stopping = false;
const interval = setInterval(processBatch, 10000);

process.on('SIGTERM', async () => {
  if (stopping) return;
  stopping = true;

  clearInterval(interval);
  await closeResources();
  process.exitCode = 0;
});

O guia de Graceful Shutdown no Node.js detalha sinais, servidores e recursos.

Erros comuns

  • Esperar precisão absoluta: timers dependem da disponibilidade do event loop.
  • Usar intervalo para tarefa lenta: execuções podem se acumular.
  • Esquecer clearTimeout: callbacks continuam depois do encerramento lógico.
  • Usar Promise.race sem cancelar: a operação externa permanece ativa.
  • Criar timers por requisição sem limite: memória e callbacks crescem.
  • Executar trabalho pesado no callback: outros timers e I/O ficam atrasados.
  • Usar timer para job crítico: reinícios apagam o agendamento.

Boas práticas para produção

  • Trate o atraso como mínimo, não como horário exato.
  • Evite sobreposição de tarefas periódicas.
  • Cancele timers quando o contexto terminar.
  • Use AbortSignal para cancelar a operação real.
  • Monitore event loop lag e duração dos callbacks.
  • Aplique unref() apenas em tarefas opcionais.
  • Use filas persistentes para jobs importantes.
  • Inclua timers no fluxo de graceful shutdown.
  • Teste com relógio falso quando apropriado.
  • Registre timeouts e cancelamentos sem expor dados sensíveis.

Conclusão

Os Timers no Node.js oferecem muito mais que um simples atraso. Eles ajudam a coordenar timeouts, intervalos, inatividade, retries e divisão de trabalho, mas continuam subordinados ao event loop.

Ao cancelar timers corretamente, evitar intervalos sobrepostos, usar AbortSignal e escolher mecanismos persistentes para tarefas críticas, você reduz vazamentos, atrasos inesperados e operações abandonadas. O resultado é um serviço mais previsível e fácil de encerrar, testar e monitorar.

Os 10 Melhores Cursos de Programação de 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