Hola colegas Había una vez en Habré
un artículo escrito bajo la autoría de John Rezig solo sobre este tema. Han pasado 10 años y el tema aún requiere aclaración. Por lo tanto, ofrecemos a los interesados leer el artículo de Samer Buna, que ofrece no solo una descripción teórica de los temporizadores en JavaScript (en el contexto de Node.js), sino también tareas sobre ellos.

Hace unas semanas tuiteé la siguiente pregunta de una sola entrevista:
“¿Dónde está el código fuente para las funciones setTimeout y setInterval? ¿Dónde lo buscarías? No puedes googlearlo :) "
*** Responde por ti mismo y luego sigue leyendo ***
Alrededor de la mitad de las respuestas a este tweet fueron incorrectas. ¡No, el caso NO ESTÁ RELACIONADO con V8 (u otras máquinas virtuales)! Funciones como
setTimeout
y
setInterval
, orgullosamente llamados JavaScript JavaScript Timers, no son parte de ninguna especificación ECMAScript o implementación de motor JavaScript. Las funciones del temporizador se implementan a nivel del navegador, por lo que su implementación difiere en los diferentes navegadores. Los temporizadores también se implementan de forma nativa en el tiempo de ejecución de Node.js.
En los navegadores, las funciones principales del temporizador se refieren a la interfaz de
Window
, que también está asociada con algunas otras funciones y objetos. Esta interfaz proporciona acceso global a todos sus elementos en el ámbito principal de JavaScript. Es por eso que la función
setTimeout
se puede ejecutar directamente en la consola del navegador.
En Node, los temporizadores son parte del objeto
global
, que está diseñado como la interfaz del navegador de
Window
. El código fuente de los temporizadores en Node se muestra
aquí .
Puede parecerle a alguien que esta es solo una mala pregunta de la entrevista: ¿de qué sirve saber esto? Yo, como desarrollador de JavaScript, pienso de esta manera: se supone que debe saber esto, ya que lo contrario puede indicar que no comprende del todo cómo V8 (y otras máquinas virtuales) interactúan con los navegadores y Node.
Veamos algunos ejemplos y resuelva un par de tareas de temporizador, ¿vamos?
Puede usar el comando de nodo para ejecutar los ejemplos en este artículo. La mayoría de los ejemplos discutidos aquí aparecen en mi curso de Introducción a Node.js en Pluralsight.Ejecución diferida de funcionesLos temporizadores son funciones de orden superior con las que puede retrasar o repetir la ejecución de otras funciones (el temporizador recibe una función como el primer argumento).
Aquí hay un ejemplo de ejecución diferida:
En este ejemplo, usando
setTimeout
el mensaje de saludo se retrasa 4 segundos. El segundo argumento para
setTimeout
es el retraso (en ms). Multiplico 4 por 1000 para obtener 4 segundos.
El primer argumento para
setTimeout
es una función cuya ejecución se retrasará.
Si ejecuta el archivo
example1.js
con el comando de nodo, Node hará una pausa durante 4 segundos y luego mostrará un mensaje de bienvenida (seguido de una salida).
Tenga en cuenta: el primer argumento para
setTimeout
es solo una
referencia de función . No debería ser una función incorporada, como
example1.js
. Aquí está el mismo ejemplo sin usar la función incorporada:
const func = () => { console.log('Hello after 4 seconds'); }; setTimeout(func, 4 * 1000);
Pasando argumentosSi la función para la cual se usa
setTimeout
para retrasar acepta cualquier argumento, entonces puede usar los argumentos restantes de la función
setTimeout
sí (después de los 2 que ya hemos estudiado) para transferir los valores de los argumentos a la función diferida.
Aquí hay un ejemplo:
La función de
rocks
anterior, retrasada por 2 segundos, toma el argumento
who
, y al llamar a
setTimeout
pasa el valor "Node.js" como tal argumento
who
.
Al ejecutar
example2.js
con el comando de
node
, se mostrará la frase "Node.js rocks" después de 2 segundos.
Temporizadores Tarea # 1Entonces, según el material ya estudiado sobre
setTimeout
,
setTimeout
los 2 mensajes siguientes después de los retrasos correspondientes.
- El mensaje "Hola después de 4 segundos" se muestra después de 4 segundos.
- El mensaje "Hola después de 8 segundos" se muestra después de 8 segundos.
LimitaciónEn su solución, puede definir solo una función que contenga funciones integradas. Esto significa que muchas llamadas
setTimeout
tendrán que usar la misma función.
SoluciónAsí es como resolvería este problema:
Para mí,
theOneFunc
recibe el argumento de
delay
y usa el valor de este argumento de
delay
en el mensaje que se muestra en la pantalla. Por lo tanto, la función puede mostrar diferentes mensajes dependiendo de qué valor de retraso le informaremos.
Luego utilicé el
theOneFunc
en dos llamadas
setTimeout
, la primera llamada se
setTimeout
después de 4 segundos y la segunda después de 8 segundos. Ambas llamadas
setTimeout
también reciben un tercer argumento, que representa el argumento de
delay
para el
theOneFunc
.
Al ejecutar el archivo
solution1.js
con el comando de nodo, mostraremos los requisitos de la tarea, y el primer mensaje aparecerá después de 4 segundos y el segundo después de 8 segundos.
Repite la funciónPero, ¿qué pasa si le pido que muestre un mensaje cada 4 segundos, por tiempo ilimitado?
Por supuesto, puede
setTimeout
en un bucle, pero la API del temporizador también ofrece la función
setInterval
, con la que puede programar la ejecución "eterna" de cualquier operación.
Aquí hay un ejemplo de
setInterval
:
Este código mostrará un mensaje cada 3 segundos. Si ejecuta
example3.js
con el comando de
node
, Node generará este comando hasta que fuerce el final del proceso (CTRL + C).
Cancelar temporizadoresComo se asigna una acción cuando se llama a la función del temporizador, esta acción también se puede deshacer antes de ejecutarse.
La llamada
setTimeout
devuelve una ID de temporizador, y puede usar esta ID de temporizador cuando llame a
clearTimeout
para cancelar el temporizador. Aquí hay un ejemplo:
Este temporizador simple debería activarse después de 0 ms (es decir, inmediatamente), pero esto no sucederá, ya que capturamos el valor de
timerId
y cancelamos inmediatamente este temporizador llamando a
clearTimeout
.
Al ejecutar
example4.js
con el comando de
node
, Node no imprimirá nada; el proceso simplemente finalizará de inmediato.
Por cierto, Node.js también proporciona otra forma de establecer
setTimeout
con un valor de 0 ms. Hay otra función en la API del temporizador Node.js llamada
setImmediate
, y básicamente hace lo mismo que
setTimeout
con un valor de 0 ms, pero en este caso puede omitir el retraso:
setImmediate( () => console.log('I am equivalent to setTimeout with 0 ms'), );
La función
setImmediate
es
compatible con todos los navegadores . No lo use en el código del cliente.
Junto con
clearTimeout
hay una función
clearInterval
que hace lo mismo, pero con llamadas
setInerval
, y también hay una llamada
clearImmediate
.
Retardo del temporizador: algo no garantizado¿Ha notado que en el ejemplo anterior, al realizar una operación con
setTimeout
después de 0 ms, esta operación no ocurre inmediatamente (después de
setTimeout
), sino solo después de que todo el código del script se haya ejecutado por completo (incluida la llamada
clearTimeout
)?
Permítanme aclarar este punto con un ejemplo. Aquí hay una llamada simple
setTimeout
que debería funcionar en medio segundo, pero esto no sucede:
Inmediatamente después de definir el temporizador en este ejemplo, bloqueamos sincrónicamente el entorno de tiempo de ejecución con un bucle
for
grande. El valor de
1e10
es 1 con 10 ceros, por lo que el ciclo dura 10 mil millones de ciclos de procesador (en principio, esto simula un procesador sobrecargado). El nodo no puede hacer nada hasta que este ciclo se complete.
Por supuesto, en la práctica esto es muy malo, pero este ejemplo ayuda a comprender que el retraso
setTimeout
no está garantizado, sino el
valor mínimo . Un valor de 500 ms significa que el retraso durará al menos 500 ms. De hecho, el script tardará mucho más en mostrar la línea de bienvenida en la pantalla. Primero, tendrá que esperar hasta que se complete el ciclo de bloqueo.
Temporizadores Problema # 2Escriba un script que muestre el mensaje "Hola mundo" una vez por segundo, pero solo 5 veces. Después de 5 iteraciones, el script debe mostrar un mensaje "Listo", después del cual se completará el proceso Nodo.
Limitación : al resolver este problema, no puede llamar a
setTimeout
.
Sugerencia : necesita un contador.
SoluciónAsí es como resolvería este problema:
let counter = 0; const intervalId = setInterval(() => { console.log('Hello World'); counter += 1; if (counter === 5) { console.log('Done'); clearInterval(intervalId); } }, 1000);
Establecí 0 como el valor inicial del
counter
, y luego llamé a
setInterval
, que toma su id.
Una función diferida mostrará un mensaje y cada vez aumentará el contador en uno. Dentro de la función diferida, tenemos una instrucción if, que verificará si ya han pasado 5 iteraciones. Después de 5 iteraciones, el programa muestra "Listo" y borra el valor del intervalo utilizando la constante del
intervalId
. Del
intervalId
capturado. El intervalo de retraso es de 1000 ms.
¿Quién llama exactamente a las funciones diferidas?Al usar JavaScript,
this
dentro de una función regular, como esta, por ejemplo:
function whoCalledMe() { console.log('Caller is', this); }
el valor en
this
coincidirá con la
persona que llama . Si define la función anterior dentro del Node REPL, entonces el objeto
global
llamará. Si define una función en la consola del navegador, el objeto de la
window
llamará.
Definamos una función como una propiedad de un objeto para hacerlo un poco más claro:
const obj = { id: '42', whoCalledMe() { console.log('Caller is', this); } };
Ahora, cuando usaremos directamente el enlace cuando
obj.whoCallMe
con la función
obj.whoCallMe
, el objeto
obj
(identificado por su
id
) actuará como la persona que llama:

Ahora la pregunta es: ¿quién será la persona que llama si pasa el enlace a
obj.whoCallMe
a
setTimetout
?
¿Quién es la persona que llama en este caso?La respuesta diferirá según dónde se ejecute la función del temporizador. En este caso, la dependencia de quién es la persona que llama es simplemente inaceptable. Perderá el control sobre la persona que llama, porque dependerá de la implementación del temporizador que en este caso llama a su función. Si prueba este código en un Node REPL, el objeto
Timeout
será el que llama:

Tenga en cuenta: esto es importante solo cuando el JavaScript
this
utiliza dentro de las funciones regulares. Al usar las funciones de flecha, la persona que llama no debería molestarte en absoluto.
Temporizadores Problema # 3Escriba un script que muestre continuamente un mensaje de "Hola mundo" con diferentes demoras. Comience con un retraso de un segundo y luego aumente en un segundo en cada iteración. En la segunda iteración, el retraso será de 2 segundos. En el tercero, tres, y así sucesivamente.
Incluya un retraso en el mensaje que se muestra. Deberías obtener algo como esto:
Hello World. 1
Hello World. 2
Hello World. 3
...
Limitaciones : las variables solo se pueden definir usando const. Usar let o var no lo es.
SoluciónDado que la duración de la demora en esta tarea es variable, no puede usar
setInterval
aquí, pero puede configurar manualmente la ejecución de intervalos usando
setTimeout
dentro de una llamada recursiva. La primera función ejecutada con
setTimeout
creará el siguiente temporizador, y así sucesivamente.
Además, dado que no puede usar
let
/
var
, no podemos tener un contador para aumentar el retraso de cada llamada recursiva; en su lugar, puede usar los argumentos de una función recursiva para realizar un incremento durante una llamada recursiva.
Aquí se explica cómo resolver este problema:
const greeting = delay => setTimeout(() => { console.log('Hello World. ' + delay); greeting(delay + 1); }, delay * 1000); greeting(1);
Temporizadores Tarea # 4Escriba una secuencia de comandos que muestre el mensaje "Hola mundo" con la misma estructura de retraso que en la tarea n.º 3, pero esta vez en grupos de 5 mensajes, y el grupo tendrá un intervalo de retraso principal. Para el primer grupo de 5 mensajes, seleccionamos el retraso inicial de 100 ms, para el siguiente - 200 ms, para el tercero - 300 ms y así sucesivamente.
Así es como debería funcionar este script:
- A 100 ms, el script muestra "Hola Mundo" por primera vez, y lo hace 5 veces con un intervalo que aumenta en 100 ms. El primer mensaje aparecerá después de 100 ms, el segundo después de 200 ms, etc.
- Después de los primeros 5 mensajes, el script debería aumentar el retraso principal en 200 ms. Por lo tanto, el sexto mensaje se mostrará después de 500 ms + 200 ms (700 ms), séptimo - 900 ms, octavo mensaje - después de 1100 ms, y así sucesivamente.
- Después de 10 mensajes, el script debería aumentar el intervalo de retraso principal en 300 ms. El undécimo mensaje debe mostrarse después de 500 ms + 1000 ms + 300 ms (18000 ms). El mensaje 12 debe mostrarse después de 2100 ms, etc.
De acuerdo con este principio, el programa debería funcionar indefinidamente.
Incluya un retraso en el mensaje que se muestra. Deberías obtener algo como esto (sin comentarios):
Hello World. 100 // 100
Hello World. 100 // 200
Hello World. 100 // 300
Hello World. 100 // 400
Hello World. 100 // 500
Hello World. 200 // 700
Hello World. 200 // 900
Hello World. 200 // 1100
...
Limitaciones : puede usar solo llamadas para establecer
setInterval
(y no para establecer
setTimeout
) y solo UNA
if
.
SoluciónComo solo podemos trabajar con llamadas
setInterval
, aquí debemos usar la recursividad y también aumentar el retraso de la próxima llamada
setInterval
. Además, necesitamos la
if
para que esto suceda solo después de 5 llamadas a esta función recursiva.
Aquí hay una posible solución:
let lastIntervalId, counter = 5; const greeting = delay => { if (counter === 5) { clearInterval(lastIntervalId); lastIntervalId = setInterval(() => { console.log('Hello World. ', delay); greeting(delay + 100); }, delay); counter = 0; } counter += 1; }; greeting(100);
Gracias a todos los que lo leyeron.