Es posible que decidamos ejecutar una función no ahora mismo, sino en un momento determinado más adelante. Eso se llama "programar una llamada".
Hay dos métodos para ello:
setTimeout
nos permite ejecutar una función una vez después del intervalo de tiempo.
setInterval
nos permite ejecutar una función repetidamente, comenzando después del intervalo de tiempo y luego repitiéndola continuamente en ese intervalo.
Estos métodos no forman parte de la especificación de JavaScript. Pero la mayoría de los entornos tienen el programador interno y proporcionan estos métodos. En particular, son compatibles con todos los navegadores y Node.js.
La sintaxis:
let timerId = setTimeout(func|código, [retraso], [arg1], [arg2], ...)
Parámetros:
func|code
Función o cadena de código a ejecutar. Por lo general, esa es una función. Por razones históricas, se puede pasar una cadena de código, pero no se recomienda.
delay
El retraso antes de la ejecución, en milisegundos (1000 ms = 1 segundo), por defecto 0.
arg1
, arg2
…
Argumentos a favor de la función
Por ejemplo, este código llama sayHi()
después de un segundo:
función decir Hola() { alerta('Hola'); } setTimeout(di Hola, 1000);
Con argumentos:
función decir Hola (frase, quién) { alerta( frase + ', ' + quién ); } setTimeout(decirHola, 1000, "Hola", "Juan"); // Hola, Juan
Si el primer argumento es una cadena, JavaScript crea una función a partir de ella.
Entonces esto también funcionará:
setTimeout("alerta('Hola')", 1000);
Pero no se recomienda usar cadenas, use funciones de flecha en lugar de ellas, como esta:
setTimeout(() => alerta('Hola'), 1000);
Pasar una función, pero no ejecutarla.
Los desarrolladores novatos a veces cometen el error de agregar corchetes ()
después de la función:
// ¡equivocado! setTimeout(decirHola(), 1000);
Eso no funciona porque setTimeout
espera una referencia a una función. Y aquí sayHi()
ejecuta la función y el resultado de su ejecución se pasa a setTimeout
. En nuestro caso, el resultado de sayHi()
no está undefined
(la función no devuelve nada), por lo que no hay nada programado.
Una llamada a setTimeout
devuelve un timerId
de "identificador de temporizador" que podemos usar para cancelar la ejecución.
La sintaxis para cancelar:
let timerId = setTimeout(...); clearTimeout(temporizadorId);
En el código siguiente, programamos la función y luego la cancelamos (cambiamos de opinión). Como resultado, no pasa nada:
let timerId = setTimeout(() => alert("nunca sucede"), 1000); alerta(identificador del temporizador); // identificador del temporizador clearTimeout(temporizadorId); alerta(identificador del temporizador); // mismo identificador (no se vuelve nulo después de cancelar)
Como podemos ver en el resultado alert
, en un navegador el identificador del temporizador es un número. En otros entornos, esto puede ser otra cosa. Por ejemplo, Node.js devuelve un objeto de temporizador con métodos adicionales.
Nuevamente, no existe una especificación universal para estos métodos, así que está bien.
Para los navegadores, los temporizadores se describen en la sección de temporizadores de HTML Living Standard.
El método setInterval
tiene la misma sintaxis que setTimeout
:
let timerId = setInterval(func|código, [retraso], [arg1], [arg2], ...)
Todos los argumentos tienen el mismo significado. Pero a diferencia de setTimeout
ejecuta la función no sólo una vez, sino regularmente después de un intervalo de tiempo determinado.
Para detener más llamadas, debemos llamar clearInterval(timerId)
.
El siguiente ejemplo mostrará el mensaje cada 2 segundos. Después de 5 segundos, la salida se detiene:
// repetir con el intervalo de 2 segundos let timerId = setInterval(() => alerta('tick'), 2000); //después de 5 segundos parar setTimeout(() => { clearInterval(timerId); alerta('detener'); }, 5000);
El tiempo pasa mientras se muestra alert
En la mayoría de los navegadores, incluidos Chrome y Firefox, el temporizador interno continúa "marcando" mientras muestra alert/confirm/prompt
.
Entonces, si ejecuta el código anterior y no cierra la ventana alert
durante algún tiempo, la siguiente alert
se mostrará inmediatamente cuando lo haga. El intervalo real entre alertas será inferior a 2 segundos.
Hay dos formas de ejecutar algo con regularidad.
Uno es setInterval
. El otro es un setTimeout
anidado, como este:
/** en lugar de: let timerId = setInterval(() => alerta('tick'), 2000); */ let timerId = setTimeout(función tick() { alerta('marca'); timerId = setTimeout(tick, 2000); // (*) }, 2000);
El setTimeout
anterior programa la siguiente llamada justo al final de la actual (*)
.
El setTimeout
anidado es un método más flexible que setInterval
. De esta manera la próxima convocatoria podrá programarse de forma diferente, dependiendo de los resultados de la actual.
Por ejemplo, necesitamos escribir un servicio que envíe una solicitud al servidor cada 5 segundos solicitando datos, pero en caso de que el servidor esté sobrecargado, debería aumentar el intervalo a 10, 20, 40 segundos…
Aquí está el pseudocódigo:
dejar retraso = 5000; let timerId = setTimeout(solicitud de función() { ...enviar solicitud... if (la solicitud falló debido a una sobrecarga del servidor) { // aumenta el intervalo para la siguiente ejecución retraso *= 2; } timerId = setTimeout(solicitud, retraso); }, demora);
Y si las funciones que estamos programando consumen mucha CPU, entonces podemos medir el tiempo que lleva la ejecución y planificar la próxima llamada tarde o temprano.
setTimeout
anidado permite establecer el retraso entre las ejecuciones con mayor precisión que setInterval
.
Comparemos dos fragmentos de código. El primero usa setInterval
:
sea yo = 1; establecerInterval(función() { función(i++); }, 100);
El segundo usa setTimeout
anidado:
sea yo = 1; setTimeout(función ejecutar() { función(i++); setTimeout(ejecutar, 100); }, 100);
Para setInterval
el programador interno ejecutará func(i++)
cada 100 ms:
¿Te diste cuenta?
¡El retraso real entre llamadas func
para setInterval
es menor que en el código!
Eso es normal, porque el tiempo que tarda la ejecución de func
"consume" una parte del intervalo.
Es posible que la ejecución de func
sea más larga de lo esperado y demore más de 100 ms.
En este caso, el motor espera a que se complete func
, luego verifica el programador y, si se acaba el tiempo, lo ejecuta nuevamente inmediatamente .
En el caso límite, si la función siempre se ejecuta por más tiempo que delay
ms, entonces las llamadas se realizarán sin ninguna pausa.
Y aquí está la imagen del setTimeout
anidado:
El setTimeout
anidado garantiza el retraso fijo (aquí 100 ms).
Esto se debe a que está prevista una nueva convocatoria al final de la anterior.
Recolección de basura y devolución de llamada setInterval/setTimeout
Cuando se pasa una función en setInterval/setTimeout
, se crea una referencia interna y se guarda en el programador. Evita que la función sea recolectada como basura, incluso si no hay otras referencias a ella.
// la función permanece en la memoria hasta que el planificador la llama setTimeout(función() {...}, 100);
Para setInterval
la función permanece en la memoria hasta que se llama clearInterval
.
Hay un efecto secundario. Una función hace referencia al entorno léxico externo, por lo que, mientras vive, las variables externas también viven. Es posible que requieran mucha más memoria que la función en sí. Entonces, cuando ya no necesitemos la función programada, es mejor cancelarla, aunque sea muy pequeña.
Hay un caso de uso especial: setTimeout(func, 0)
o simplemente setTimeout(func)
.
Esto programa la ejecución de func
lo antes posible. Pero el programador lo invocará sólo después de que se complete el script que se está ejecutando actualmente.
Por lo tanto, la función está programada para ejecutarse "justo después" del script actual.
Por ejemplo, esto genera "Hola", luego inmediatamente "Mundo":
setTimeout(() => alerta("Mundo")); alerta("Hola");
La primera línea "coloca la llamada en el calendario después de 0 ms". Pero el programador sólo "verificará el calendario" después de que se complete el script actual, por lo que "Hello"
está primero y "World"
después.
También existen casos de uso avanzados relacionados con el navegador de tiempo de espera sin retardo, que analizaremos en el capítulo Bucle de eventos: microtareas y macrotareas.
De hecho, el retraso cero no es cero (en un navegador)
En el navegador, existe una limitación en la frecuencia con la que se pueden ejecutar los temporizadores anidados. HTML Living Standard dice: "después de cinco temporizadores anidados, el intervalo se fuerza a ser de al menos 4 milisegundos".
Demostremos lo que significa con el siguiente ejemplo. La llamada setTimeout
que contiene se reprograma sin demora. Cada llamada recuerda el tiempo real de la anterior en la matriz times
. ¿Cómo son los verdaderos retrasos? Vamos a ver:
dejar empezar = Fecha.ahora(); dejar tiempos = []; setTimeout(función ejecutar() { times.push(Fecha.ahora() - inicio); // recuerda el retraso de la llamada anterior if (inicio + 100 < Fecha.ahora()) alerta(veces); //muestra los retrasos después de 100ms de lo contrario setTimeout(ejecutar); // de lo contrario reprogramar }); // un ejemplo de la salida: // 1,1,1,1,9,15,20,24,30,35,40,45,50,55,59,64,70,75,80,85,90,95,100
Los primeros temporizadores se ejecutan inmediatamente (tal como está escrito en la especificación), y luego vemos 9, 15, 20, 24...
Entra en juego el retraso obligatorio de 4+ ms entre invocaciones.
Lo mismo sucede si usamos setInterval
en lugar de setTimeout
: setInterval(f)
se ejecuta f
varias veces con cero retraso y luego con un retraso de más de 4 ms.
Esa limitación proviene de la antigüedad y muchas escrituras se basan en ella, por lo que existe por razones históricas.
Para JavaScript del lado del servidor, esa limitación no existe y existen otras formas de programar un trabajo asincrónico inmediato, como setImmediate para Node.js. Entonces esta nota es específica del navegador.
Los métodos setTimeout(func, delay, ...args)
y setInterval(func, delay, ...args)
nos permiten ejecutar la func
una vez o regularmente después de delay
de milisegundos.
Para cancelar la ejecución, debemos llamar clearTimeout/clearInterval
con el valor devuelto por setTimeout/setInterval
.
Las llamadas anidadas setTimeout
son una alternativa más flexible a setInterval
, lo que nos permite establecer el tiempo entre ejecuciones con mayor precisión.
La programación con retardo cero con setTimeout(func, 0)
(igual que setTimeout(func)
) se utiliza para programar la llamada "lo antes posible, pero después de que se complete el script actual".
El navegador limita el retraso mínimo para cinco o más llamadas anidadas de setTimeout
o setInterval
(después de la quinta llamada) a 4 ms. Eso es por razones históricas.
Tenga en cuenta que todos los métodos de programación no garantizan el retraso exacto.
Por ejemplo, el temporizador del navegador puede ralentizarse por muchos motivos:
La CPU está sobrecargada.
La pestaña del navegador está en modo de fondo.
La computadora portátil está en modo de ahorro de batería.
Todo eso puede aumentar la resolución mínima del temporizador (el retraso mínimo) a 300 ms o incluso 1000 ms dependiendo del navegador y la configuración de rendimiento a nivel del sistema operativo.
importancia: 5
Escriba una función printNumbers(from, to)
que genere un número cada segundo, comenzando desde from
y terminando con to
.
Haz dos variantes de la solución.
Usando setInterval
.
Usando setTimeout
anidado.
Usando setInterval
:
función imprimirNúmeros(desde, hasta) { dejar actual = de; let timerId = setInterval(función() { alerta (actual); si (actual == a) { clearInterval(temporizadorId); } actual++; }, 1000); } // uso: imprimirNúmeros(5, 10);
Usando setTimeout
anidado:
función imprimirNúmeros(desde, hasta) { dejar actual = de; setTimeout(función ir() { alerta (actual); si (actual < a) { setTimeout(ir, 1000); } actual++; }, 1000); } // uso: imprimirNúmeros(5, 10);
Tenga en cuenta que en ambas soluciones hay un retraso inicial antes de la primera salida. La función se llama después de 1000ms
la primera vez.
Si también queremos que la función se ejecute inmediatamente, podemos agregar una llamada adicional en una línea separada, como esta:
función imprimirNúmeros(desde, hasta) { dejar actual = de; función ir() { alerta (actual); si (actual == a) { clearInterval(temporizadorId); } actual++; } ir(); let timerId = setInterval(go, 1000); } imprimirNúmeros(5, 10);
importancia: 5
En el código siguiente hay una llamada setTimeout
programada, luego se ejecuta un cálculo pesado, que tarda más de 100 ms en finalizar.
¿Cuándo se ejecutará la función programada?
Después del bucle.
Antes del bucle.
Al comienzo del bucle.
¿Qué va a mostrar alert
?
sea yo = 0; setTimeout(() => alerta(i), 100); // ? // suponemos que el tiempo para ejecutar esta función es >100ms para(sea j = 0; j < 100000000; j++) { yo ++; }
Cualquier setTimeout
se ejecutará solo después de que haya finalizado el código actual.
El i
seré el último: 100000000
.
sea yo = 0; setTimeout(() => alerta(i), 100); // 100000000 // suponemos que el tiempo para ejecutar esta función es >100ms para(sea j = 0; j < 100000000; j++) { yo ++; }