¿Cómo funciona `setTimeout` y por qué el retraso que le pasas no es exacto?
Ver respuesta — intenta responderla en voz alta primero
setTimeout programa una función para que se ejecute después de un tiempo mínimo, no exacto. El callback se coloca en la cola de macrotareas y solo se ejecuta cuando el call stack está vacío y las microtareas ya se procesaron. Por eso el retraso es un mínimo garantizado, no una promesa de precisión.
setTimeout(callback, ms) no ejecuta el callback exactamente a los ms milisegundos. Lo que hace es: esperar ese tiempo mínimo y luego colocar el callback en la cola de macrotareas. El callback solo se ejecuta cuando:
- El código síncrono actual ha terminado (call stack vacío).
- Todas las microtareas pendientes (promesas) se han procesado.
Por eso setTimeout(fn, 0) no se ejecuta de inmediato: espera a que todo el código síncrono termine.
// El retraso es un mínimo, no exacto
console.log("inicio");
setTimeout(() => console.log("después de ~0ms"), 0);
console.log("fin");
// Salida: inicio, fin, después de ~0ms
// El código síncrono retrasa el timeout incluso cuando está en 0ms
setTimeout(() => console.log("timeout"), 0);
for (let i = 0; i < 1e9; i++) {} // bucle pesado y bloqueante
console.log("bucle terminado");
// "bucle terminado" aparece ANTES que "timeout",
// porque el timeout espera a que se libere el hilo.
// El bug clásico: var en un bucle con setTimeout
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
// Imprime 3, 3, 3 (con var, todos comparten el mismo i)
// Con let se arregla: cada iteración tiene su propio i
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log(j), 0);
}
// Imprime 0, 1, 2
- Creer que
setTimeout(fn, 1000)se ejecuta exactamente al segundo. Si el hilo está ocupado, se retrasa. - El bug del bucle con
var: comovarno tiene alcance de bloque, todos los callbacks comparten la misma variable, que para cuando se ejecutan ya tiene el valor final. Se arregla conlet.
"
setTimeoutprograma un callback para que se ejecute después de un tiempo mínimo, no exacto. Cuando pasa ese tiempo, el callback va a la cola de macrotareas y solo se ejecuta cuando el call stack está vacío y las microtareas ya se procesaron. Por eso unsetTimeoutde 0 no se ejecuta de inmediato: espera a que el código síncrono termine. Un caso clásico es el bucle convarysetTimeout, que imprime el valor final repetido porque todas las iteraciones comparten la misma variable; se arregla usandolet."
¿Qué imprime esto y cómo lo arreglarías para que imprima 0, 1, 2?
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
Ver respuesta
Imprime 3, 3, 3. Se arregla cambiando var por let, que crea una nueva variable i en cada iteración, de modo que cada callback recuerda su propio valor e imprime 0, 1, 2.