JavaScript setTimeout y setInterval
Aprende a programar código en JavaScript con setTimeout y setInterval: sintaxis, argumentos, cancelación de temporizadores, setTimeout recursivo, comportamiento de retardo cero, debouncing y throttling.
A veces no quieres ejecutar código en este momento — quieres ejecutarlo más tarde, o ejecutarlo repetidamente. Las dos funciones de programación de JavaScript, setTimeout() y setInterval(), te permiten hacer exactamente eso. Esta guía cubre su sintaxis, cómo pasar argumentos, cómo cancelar un temporizador programado, el importante patrón de "setTimeout recursivo", el sorprendente comportamiento de un retardo de cero, y dos usos del mundo real: debouncing y throttling.
Ninguna de las dos funciones forma parte del lenguaje JavaScript central — las proporciona el entorno anfitrión (navegadores y Node.js). El comportamiento descrito aquí es el mismo en ambos, con algunas diferencias señaladas a lo largo del camino.
Introducción a las funciones de temporización de JavaScript
Un temporizador programa un callback para que se ejecute después de que el código actual termine y haya pasado una cantidad de tiempo determinada. Dado que JavaScript es de un solo hilo, el callback nunca interrumpe el código en ejecución; espera en una cola y se ejecuta solo cuando la pila de llamadas está vacía. (Para conocer el mecanismo completo, consulta The Event Loop.)
La función setTimeout()
setTimeout() ejecuta una función una vez después de un retardo especificado. Acepta una función a ejecutar y un retardo en milisegundos antes de esa ejecución.
Sintaxis
let timerId = setTimeout(func, delay, arg1, arg2, ...);func— la función (o, menos comúnmente, una cadena de código) a ejecutar.delay— la espera en milisegundos antes de ejecutar. Por defecto es0.arg1, arg2, ...— argumentos opcionales que se pasan directamente afunc.
El valor de retorno es un id de temporizador numérico que puedes pasar posteriormente a clearTimeout().
Ejemplo
Pasar argumentos al callback
Todo lo que pongas después del retardo se reenvía al callback. Esto es más limpio que envolver la llamada en otra función flecha:
setTimeout(greet(), 1000) ejecuta greet() inmediatamente y programa su valor de retorno (probablemente undefined). Escribe setTimeout(greet, 1000) — sin paréntesis.La función setInterval()
setInterval() tiene la misma firma que setTimeout(), pero en lugar de ejecutar el callback una vez, lo ejecuta repetidamente cada delay milisegundos hasta que lo detengas.
Sintaxis
let timerId = setInterval(func, delay, arg1, arg2, ...);Ejemplo
setTimeout recursivo que se describe a continuación.setTimeout recursivo vs. setInterval
Puedes reproducir setInterval() haciendo que un callback de setTimeout() se reprograme a sí mismo. La diferencia clave: setInterval() mide el retardo entre inicios, mientras que el setTimeout() recursivo lo mide entre el final de una ejecución y el inicio de la siguiente — garantizando una pausa fija incluso cuando el callback es lento.
Cancelar la ejecución programada
Ambas funciones devuelven un id de temporizador. Conserva ese id y podrás cancelar el trabajo programado con clearTimeout() o clearInterval(). (Las dos funciones de cancelación son en realidad intercambiables en la mayoría de los motores, pero hacer coincidir cada una con el programador que creó el id mantiene el código legible.)
Detener setTimeout()
Para cancelar un timeout, guarda el id devuelto por setTimeout() y pásalo a clearTimeout() antes de que transcurra el retardo.
Ejemplo
Detener setInterval()
De manera similar, guarda el id de setInterval() y pásalo a clearInterval(). Sin esto, el intervalo se ejecuta indefinidamente (o hasta que se cierre la página), lo que es una fuente común de fugas de memoria y temporizadores descontrolados.
Ejemplo
El setTimeout con retardo cero
setTimeout(func, 0) no ejecuta func inmediatamente. Programa func para que se ejecute tan pronto como el código sincrónico actual haya terminado. Esta es una forma práctica de "ceder" — para dejar que el navegador repinte o para dividir una tarea larga en partes — y explica el orden de salida que a menudo sorprende a los principiantes:
Ten en cuenta que los temporizadores son macrotareas: se ejecutan después de todas las microtareas en cola (como los callbacks de promesas resueltas). Consulta Event loop: microtasks and macrotasks para conocer las reglas de ordenamiento precisas.
Aplicaciones prácticas y consejos
Dos de los usos más comunes del mundo real de setTimeout() son el debouncing y el throttling — ambas son formas de limitar la frecuencia con la que se ejecuta una función en respuesta a eventos rápidos.
Debouncing con setTimeout()
El debouncing espera hasta que una ráfaga de eventos se haya detenido antes de ejecutar la función. Cada nuevo evento reinicia el temporizador, por lo que el callback se activa solo después de que las cosas se calmen durante wait milisegundos. Esto es ideal para un cuadro de búsqueda: quieres enviar una solicitud después de que el usuario deje de escribir, no una por cada pulsación de tecla.
<!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 con setTimeout()
El throttling ejecuta la función como máximo una vez por limit milisegundos, sin importar cuántos eventos lleguen entretanto. Donde el debouncing espera el silencio, el throttling garantiza un ritmo constante — perfecto para los manejadores de scroll, resize o mousemove que de otro modo dispararían docenas de veces por segundo. El ejemplo a continuación utiliza un enfoque de borde inicial (se ejecuta inmediatamente en el primer evento y luego impone el 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>Problemas a recordar
- Los retardos son un mínimo, no una garantía. Si la pila de llamadas está ocupada o el bucle de eventos está congestionado, el callback espera. El número que pasas es lo más pronto que puede ejecutarse, no una promesa.
- Las pestañas en segundo plano están limitadas. La mayoría de los navegadores limitan los temporizadores en pestañas inactivas a aproximadamente una vez por segundo para ahorrar energía, por lo que las animaciones y el polling se ralentizan cuando la pestaña está oculta.
- Los timeouts anidados están limitados a ~4ms. Después de cinco llamadas anidadas a
setTimeout(), los navegadores imponen un retardo mínimo de aproximadamente 4 milisegundos, por lo que un retardo de0nunca es verdaderamente cero en cadenas profundas. - Retardo máximo. Un retardo mayor que
2147483647(2^31 − 1) desborda el campo de 32 bits y se trata como0, disparándose casi inmediatamente en lugar de en el futuro lejano. - Vinculación de
this. Cuando pasas un método comosetTimeout(obj.method, 1000), pierde suthis. Usa una función flecha —setTimeout(() => obj.method(), 1000)— oobj.method.bind(obj). - Limpia siempre. Cancela los intervalos (y los timeouts pendientes) cuando un componente se desmonta o el trabajo ya no es necesario, o tendrás fugas de temporizadores y podrías operar con estado obsoleto.
Temas relacionados
- Introduction: callbacks — la base sobre la que se construyen los temporizadores.
- The Event Loop: microtasks and macrotasks — por qué un timeout de
0ms aún se ejecuta al final. - Promise y Async/await — alternativas modernas para secuenciar trabajo asíncrono.
- Recursion and stack — contexto para el patrón
setTimeoutrecursivo.
Conclusión
setTimeout() ejecuta código una vez después de un retardo; setInterval() lo ejecuta en un horario repetitivo; clearTimeout() y clearInterval() los cancelan. Recuerda que los retardos son mínimos, pasa los argumentos después del retardo en lugar de dentro de un envoltorio, recurre al patrón setTimeout recursivo cuando necesites espaciado constante, y siempre limpia los temporizadores que ya no necesites. Con debouncing y throttling además, estas dos pequeñas funciones cubren la mayor parte del trabajo basado en tiempo que harás en el navegador.