JavaScript setTimeout e setInterval
Aprenda a agendar código em JavaScript com setTimeout e setInterval: sintaxe, argumentos, cancelamento de timers, setTimeout recursivo, comportamento de atraso zero, debouncing e throttling.
Às vezes você não quer executar código agora — você quer executá-lo mais tarde, ou executá-lo repetidamente. As duas funções de agendamento do JavaScript, setTimeout() e setInterval(), permitem exatamente isso. Este guia abrange a sintaxe delas, como passar argumentos, como cancelar um timer agendado, o importante padrão de "setTimeout recursivo", o comportamento surpreendente de um atraso zero e dois usos práticos: debouncing e throttling.
Nenhuma das funções faz parte da linguagem JavaScript principal — elas são fornecidas pelo ambiente host (navegadores e Node.js). O comportamento descrito aqui é o mesmo em ambos, com algumas diferenças observadas ao longo do caminho.
Introdução às Funções de Temporização do JavaScript
Um timer agenda um callback para ser executado após o código atual terminar e uma determinada quantidade de tempo ter passado. Como o JavaScript é single-threaded, o callback nunca interrompe o código em execução; ele aguarda em uma fila e só é executado quando a pilha de chamadas está vazia. (Para o mecanismo completo, veja The Event Loop.)
A Função setTimeout()
setTimeout() executa uma função uma vez após um atraso especificado. Ela aceita uma função para executar e um atraso em milissegundos antes dessa execução.
Sintaxe
let timerId = setTimeout(func, delay, arg1, arg2, ...);func— a função (ou, menos comumente, uma string de código) a ser executada.delay— o tempo de espera em milissegundos antes da execução. O padrão é0.arg1, arg2, ...— argumentos opcionais passados diretamente parafunc.
O valor de retorno é um id numérico de timer que você pode passar posteriormente para clearTimeout().
Exemplo
Passando argumentos para o callback
Tudo que você colocar após o atraso é encaminhado para o callback. Isso é mais limpo do que envolver a chamada em outra arrow function:
setTimeout(greet(), 1000) executa greet() imediatamente e agenda seu valor de retorno (provavelmente undefined). Escreva setTimeout(greet, 1000) — sem parênteses.A Função setInterval()
setInterval() tem a mesma assinatura que setTimeout(), mas em vez de executar o callback uma vez, ela o executa repetidamente a cada delay milissegundos até você pará-la.
Sintaxe
let timerId = setInterval(func, delay, arg1, arg2, ...);Exemplo
setTimeout recursivo abaixo.setTimeout Recursivo vs. setInterval
Você pode reproduzir o comportamento de setInterval() fazendo com que um callback de setTimeout() se reagende. A diferença principal: setInterval() mede o atraso entre os inícios, enquanto o setTimeout() recursivo o mede entre o fim de uma execução e o início da próxima — garantindo uma pausa fixa mesmo quando o callback é lento.
Cancelando Execução Agendada
Ambas as funções retornam um id de timer. Guarde esse id e você poderá cancelar o trabalho agendado com clearTimeout() ou clearInterval(). (As duas funções de cancelamento são na verdade intercambiáveis na maioria dos motores, mas combiná-las com o agendador que criou o id torna o código mais legível.)
Parando o setTimeout()
Para cancelar um timeout, armazene o id retornado por setTimeout() e passe-o para clearTimeout() antes de o atraso expirar.
Exemplo
Parando o setInterval()
Da mesma forma, salve o id de setInterval() e passe-o para clearInterval(). Sem isso, o intervalo é executado indefinidamente (ou até a página ser fechada), o que é uma fonte comum de vazamentos de memória e timers descontrolados.
Exemplo
O setTimeout de atraso zero
setTimeout(func, 0) não executa func imediatamente. Ele agenda func para ser executado assim que o código síncrono atual terminar. Esta é uma forma prática de "ceder" — permitir que o navegador repinte ou dividir uma tarefa longa em partes — e explica a ordem de saída que frequentemente surpreende iniciantes:
Note que os timers são macrotasks: eles são executados após todas as microtasks enfileiradas (como callbacks de promessas resolvidas). Veja Event loop: microtasks and macrotasks para as regras de ordenação precisas.
Aplicações Práticas e Dicas
Dois dos usos práticos mais comuns de setTimeout() são debouncing e throttling — ambos são formas de limitar a frequência com que uma função é executada em resposta a eventos rápidos e sucessivos.
Debouncing com setTimeout()
Debouncing aguarda até que uma rajada de eventos tenha parado antes de executar a função. Cada novo evento redefine o timer, então o callback só dispara após as coisas ficarem quietas por wait milissegundos. Isso é ideal para uma caixa de pesquisa: você quer enviar uma requisição depois que o usuário parar de digitar, não uma por tecla pressionada.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Debounced Input Example</title>
<script>
// Debounce function to limit the rate at which a function is executed
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Function to be debounced
function fetchData(input) {
alert(`API call with input: ${input}`); // Placeholder for an API call
}
// Create a debounced version of fetchData
const debouncedFetchData = debounce(fetchData, 300);
// Add the debounced function to an event listener
function setup() {
document.getElementById('searchInput').addEventListener('input', (event) => {
debouncedFetchData(event.target.value);
});
}
// Ensure setup is called once the document is fully loaded
document.addEventListener('DOMContentLoaded', setup);
</script>
</head>
<body>
<h3>Type in the input field:</h3>
<input type="text" id="searchInput" placeholder="Start typing..." />
</body>
</html>Throttling com setTimeout()
Throttling executa a função no máximo uma vez por limit milissegundos, independentemente de quantos eventos chegam no intervalo. Onde o debouncing aguarda silêncio, o throttling garante uma cadência constante — perfeito para handlers de scroll, resize ou mousemove que de outra forma disparariam dezenas de vezes por segundo. O exemplo abaixo usa uma abordagem de leading-edge (executa imediatamente no primeiro evento, depois aplica o intervalo):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Throttled Scroll Event</title>
<style>
/* Simple styling for demonstration */
body, html {
height: 200%; /* Make the page scrollable */
margin: 0;
padding: 0;
font-family: Arial, sans-serif;
}
#log {
position: fixed;
top: 0;
left: 0;
background: white;
border: 1px solid #ccc;
padding: 10px;
width: 300px;
}
</style>
</head>
<body>
<div id="log">Scroll to see the effect...</div>
<script>
// Throttle function using setTimeout
function throttle(func, limit) {
let lastFunc;
let lastRan;
return function() {
const context = this;
const args = arguments;
if (!lastRan) {
func.apply(context, args);
lastRan = Date.now();
} else {
clearTimeout(lastFunc);
lastFunc = setTimeout(function() {
if ((Date.now() - lastRan) >= limit) {
func.apply(context, args);
lastRan = Date.now();
}
}, Math.max(0, limit - (Date.now() - lastRan)));
}
}
}
// Function to be throttled
function handleScroll() {
const log = document.getElementById('log');
log.textContent = `Scroll event triggered at: ${new Date().toLocaleTimeString()}`;
}
// Add event listener for scroll
window.addEventListener('scroll', throttle(handleScroll, 1000));
</script>
</body>
</html>Armadilhas a lembrar
- Atrasos são um mínimo, não uma garantia. Se a pilha de chamadas estiver ocupada ou o event loop congestionado, o callback aguarda. O número que você passa é o mais cedo que pode ser executado, não uma promessa.
- Abas em segundo plano são limitadas. A maioria dos navegadores restringe timers em abas inativas para aproximadamente uma vez por segundo para economizar energia, então animações e polling ficam mais lentos quando a aba está oculta.
- Timeouts aninhados são limitados a ~4ms. Após cinco chamadas aninhadas de
setTimeout(), os navegadores impõem um atraso mínimo de cerca de 4 milissegundos, então um atraso0nunca é verdadeiramente zero em cadeias profundas. - Atraso máximo. Um atraso maior que
2147483647(2^31 − 1) estoura o campo de 32 bits e é tratado como0, disparando quase imediatamente em vez de no futuro distante. - Vinculação do
this. Quando você passa um método comosetTimeout(obj.method, 1000), ele perde seuthis. Use uma arrow function —setTimeout(() => obj.method(), 1000)— ouobj.method.bind(obj). - Sempre faça limpeza. Cancele intervalos (e timeouts pendentes) quando um componente for desmontado ou o trabalho não for mais necessário, ou você vazará timers e poderá operar em estado obsoleto.
Tópicos relacionados
- Introdução: callbacks — a base sobre a qual os timers são construídos.
- The Event Loop: microtasks and macrotasks — por que um timeout de
0ms ainda é executado por último. - Promise e Async/await — alternativas modernas para sequenciar trabalho assíncrono.
- Recursão e pilha — contexto para o padrão de
setTimeoutrecursivo.
Conclusão
setTimeout() executa código uma vez após um atraso; setInterval() o executa em um cronograma repetido; clearTimeout() e clearInterval() os cancelam. Lembre-se de que os atrasos são mínimos, passe argumentos após o atraso em vez de dentro de um wrapper, recorra ao padrão de setTimeout recursivo quando precisar de espaçamento constante e sempre faça a limpeza dos timers que não são mais necessários. Com debouncing e throttling por cima, essas duas pequenas funções cobrem a maior parte do trabalho baseado em tempo que você fará no navegador.