O Timers Promises no Node.js oferece versões baseadas em Promises para timeout, immediate e interval. Em vez de receber callbacks, as funções do módulo node:timers/promises podem ser usadas com await, AbortSignal e iteração assíncrona, deixando fluxos de espera mais fáceis de compor e cancelar.
A API Timers Promises foi adicionada no Node.js 15 e deixou de ser experimental no Node.js 16. Os métodos principais são estáveis. Já scheduler.wait() e scheduler.yield() continuam experimentais na documentação do Node.js 26.5.0.
Neste guia, você aprenderá a usar setTimeout(), setImmediate(), setInterval(), opções de ref, cancelamento, deadlines, retries, polling, scheduler, testes com relógio virtual e cuidados com precisão e concorrência.
O que é node:timers/promises?
O módulo expõe timers que retornam Promises ou iteradores assíncronos. A documentação oficial de Timers Promises descreve a API. Para cancelamento, consulte a documentação de AbortSignal na MDN.
Para callbacks tradicionais, veja Timers no Node.js. Para sinais, consulte AbortController no Node.js. O artigo de Event Loop no Node.js ajuda a entender por que atrasos não são exatos.
Importando setTimeout
const {
setTimeout
} = require('node:timers/promises');
await setTimeout(1000);Depois de aproximadamente um segundo, a Promise é resolvida.
Evitando conflito de nomes
O nome é igual ao timer global. Use um alias quando ambos aparecem no mesmo arquivo:
const {
setTimeout: delay
} = require('node:timers/promises');
await delay(500);Retornando um valor
const result = await delay(
100,
{ ok: true }
);
console.log(result.ok);O segundo argumento é o valor usado para resolver a Promise. Ele não é uma função de callback.
Função sleep
async function sleep(milliseconds, signal) {
await delay(milliseconds, undefined, {
signal
});
}Um wrapper pode padronizar cancelamento e validação.
Validando o atraso
function validateDelay(value) {
if (!Number.isFinite(value) || value < 0) {
throw new TypeError('Delay inválido');
}
return Math.min(value, 60000);
}O Node.js ajusta valores inválidos ou muito grandes conforme as regras da API, mas a aplicação deve impor limites de negócio.
Cancelando com AbortSignal
const controller = new AbortController();
const promise = delay(10000, 'done', {
signal: controller.signal
});
controller.abort();
try {
await promise;
} catch (error) {
if (error.name !== 'AbortError') {
throw error;
}
}Quando cancelado, o timer rejeita com AbortError.
AbortSignal.timeout()
await operation({
signal: AbortSignal.timeout(5000)
});Quando a própria operação aceita sinal, prefira passar o deadline diretamente a ela em vez de apenas usar Promise.race().
Promise.race()
const result = await Promise.race([
request(),
delay(5000).then(() => {
throw new Error('Timeout');
})
]);Esse padrão rejeita o chamador, mas não cancela automaticamente request(). A operação pode continuar consumindo recursos.
Deadline cooperativo
const controller = new AbortController();
const timeout = delay(5000, undefined, {
signal: controller.signal
}).then(() => controller.abort());
try {
return await request({
signal: controller.signal
});
} finally {
controller.abort();
await timeout.catch(() => {});
}Uma alternativa mais simples é usar AbortSignal.timeout quando disponível.
setImmediate com Promise
const {
setImmediate
} = require('node:timers/promises');
await setImmediate();A Promise resolve na fase de immediates, depois de callbacks de I/O correspondentes ao ciclo.
Valor no setImmediate
const value = await setImmediate('ready');Yield cooperativo
Em um loop longo, aguardar setImmediate permite que o event loop processe outras tarefas:
for (let index = 0; index < items.length; index += 1) {
processItem(items[index]);
if (index % 1000 === 0) {
await setImmediate();
}
}Isso não transforma cálculo pesado em trabalho paralelo. Para CPU intensa, use Worker Threads no Node.js.
setInterval como iterador
const {
setInterval
} = require('node:timers/promises');
for await (const tick of setInterval(
1000,
'tick'
)) {
console.log(tick);
}O método retorna um iterador assíncrono que produz o valor informado a cada intervalo.
Interrompendo o intervalo
let count = 0;
for await (const tick of setInterval(1000)) {
await executeTask();
count += 1;
if (count === 5) break;
}Sair do loop encerra o consumo. Use também AbortSignal para cancelamento externo.
Intervalo cancelável
const controller = new AbortController();
try {
for await (const tick of setInterval(
1000,
Date.now(),
{ signal: controller.signal }
)) {
await poll();
}
} catch (error) {
if (error.name !== 'AbortError') {
throw error;
}
}Intervalo não é cronômetro exato
Se o processamento demora, os ticks não garantem precisão de tempo real. Event loop ocupado, sistema operacional e garbage collection podem atrasar a execução.
Polling sequencial
O for await facilita evitar sobreposição:
for await (const _ of setInterval(5000)) {
await checkStatus();
}Se checkStatus() demora mais de cinco segundos, o comportamento precisa ser medido. Em muitos casos, um loop com delay após o trabalho é mais claro.
Delay entre execuções
while (!signal.aborted) {
await checkStatus(signal);
await delay(5000, undefined, { signal });
}Esse padrão espera cinco segundos depois que cada verificação termina.
Retry com backoff
for (let attempt = 0; attempt < 4; attempt += 1) {
try {
return await operation();
} catch (error) {
if (!isRetryable(error) || attempt === 3) {
throw error;
}
const wait = 200 * 2 ** attempt;
await delay(wait);
}
}Adicione jitter para evitar várias instâncias repetindo ao mesmo tempo. Consulte Retry com Backoff no Node.js.
Backoff com jitter
const base = 200 * 2 ** attempt;
const wait = Math.floor(
Math.random() * base
);
await delay(wait, undefined, { signal });Opção ref
await delay(1000, undefined, {
ref: false
});Com ref: false, o timer não exige que o event loop continue ativo. Se não existir outro trabalho, o processo pode encerrar antes da resolução.
Quando usar ref false?
- telemetria opcional;
- cleanup não crítico;
- delay auxiliar que não deve impedir shutdown;
- polling de baixa prioridade.
Não use quando a Promise precisa obrigatoriamente concluir.
Ref e await
Mesmo que o código esteja aguardando uma Promise, um timer unref pode não manter o processo vivo. O await sozinho não é um handle ativo.
scheduler.wait()
const {
scheduler
} = require('node:timers/promises');
await scheduler.wait(1000);Na documentação do Node.js 26.5.0, scheduler.wait() é experimental e equivalente a um setTimeout sem valor.
scheduler.yield()
await scheduler.yield();Esse método experimental equivale conceitualmente a aguardar setImmediate sem valor, seguindo uma proposta de Scheduling API da plataforma Web.
Não dependa de scheduler sem fixar versão
Como os métodos scheduler são experimentais, uma biblioteca pública deve oferecer fallback ou evitar usá-los no contrato principal.
Precisão de timers
O delay representa o tempo mínimo aproximado antes da execução. O Node.js não garante que a Promise resolva exatamente naquele milissegundo.
Medindo atraso real
const start = performance.now();
await delay(100);
const elapsed = performance.now() - start;
console.log(elapsed);Use Performance Hooks no Node.js para medições.
Timers muito longos
Valores acima do limite interno não funcionam como um agendamento de meses. Para tarefas futuras, use scheduler persistente, banco ou fila.
Agendamento de negócio
Não use um Promise timer para enviar uma cobrança daqui a trinta dias. Se o processo reiniciar, o timer desaparece.
Shutdown
Associe um controller aos loops:
const shutdownController = new AbortController();
process.once('SIGTERM', () => {
shutdownController.abort();
});Todos os delays e intervalos relevantes devem receber o sinal.
Tratando AbortError
Cancelamento durante shutdown é esperado e não deve ser registrado como falha crítica:
try {
await runLoop(signal);
} catch (error) {
if (error.name !== 'AbortError') {
logger.error(error);
}
}Promises pendentes
Não crie timers sem guardar ou tratar a Promise:
delay(1000).then(runTask);Adicione catch ou aguarde, pois uma rejeição por cancelamento pode virar unhandled rejection.
Intervalos e erros
Um erro dentro do corpo encerra o loop. Decida se deve parar, registrar e continuar ou aplicar retry.
Concorrência
Evite iniciar trabalho sem aguardar em cada tick:
for await (const _ of setInterval(1000)) {
executeTask();
}Esse padrão pode acumular tarefas. Aguarde ou use um limite.
Fila limitada
Quando tarefas podem rodar em paralelo, use um pool com máximo conhecido e descarte ou adie ticks quando estiver cheio.
Rate limiting
Um delay simples pode espaçar chamadas, mas não substitui um algoritmo de token bucket para múltiplos consumidores.
Testes com timers simulados
O Node.js Test Runner oferece mock timers em versões modernas:
test('aguarda retry', t => {
t.mock.timers.enable({
apis: ['setTimeout']
});
const promise = retryOperation();
t.mock.timers.tick(1000);
return promise;
});A interação com node:timers/promises deve ser validada na versão usada.
Testando AbortSignal
test('cancela espera', async () => {
const controller = new AbortController();
const promise = delay(1000, undefined, {
signal: controller.signal
});
controller.abort();
await assert.rejects(
promise,
error => error.name === 'AbortError'
);
});Testes sem esperar tempo real
Injete uma função delay:
function createRetrier({ sleep = delay }) {
return async function retry(operation) {
// usa sleep
};
}No teste, passe uma Promise resolvida imediatamente.
Erros comuns
- Usar Promise.race sem cancelar: a operação continua.
- Intervalo com tarefa não aguardada: chamadas acumulam.
- Timer para agendamento persistente: o reinício perde a tarefa.
- Esperar precisão exata: o event loop causa atraso.
- Ignorar AbortError: surge rejeição não tratada.
- Usar ref false em tarefa crítica: o processo encerra cedo.
- Tratar scheduler como estável: APIs experimentais podem mudar.
Boas práticas
- Use alias como delay.
- Passe AbortSignal.
- Aguarde tarefas em intervalos.
- Use backoff com jitter.
- Defina limites.
- Não use para agenda persistente.
- Trate cancelamento.
- Evite precisão presumida.
- Injete delay nos testes.
- Fixe Node ao usar scheduler.
Conclusão
O Timers Promises no Node.js torna esperas, intervals e yields mais fáceis de combinar com async e await. Os métodos principais são estáveis e suportam valores, AbortSignal e a opção ref.
A aplicação ainda precisa controlar cancelamento, concorrência e persistência. Timers não são relógios exatos nem schedulers duráveis. Com sinais, backoff, loops sequenciais e testes determinísticos, a API oferece uma base limpa para esperas e coordenação assíncrona sem callbacks aninhados.




