17

  for (var i = 0; i < 5; i++) {
     setTimeout(function() { 
        console.log(i); 
     },i * 1000 );
    }

Buenas, en el siguiente trozo de código, el valor de i siempre es 5, en el console.log(). ¿Por qué?

Riaven
  • 3,379
  • 5
  • 14
  • 31
Caldeiro
  • 668
  • 5
  • 18

5 Answers5

17

Con este ejemplo se vería aproximadamente lo que ocurre con la función.

for (var i = 0; i < 5; i++) {
console.log(i);
 setTimeout(function() { 
    console.log('TimeOut: ' + i); 
 },i * 1000 );
}

Lo único que hice fue colocar un console.log() fuera de la función setTimeout y añadirle un string al que existe dentro de dicha función para diferenciar.

Si revisamos lo que nos escupe la consola podemos ver que primeramente se pinta:

14:55:01.689 0
14:55:01.689 1
14:55:01.689 2
14:55:01.689 3
14:55:01.689 4

Para posteriormente escupir:

14:55:01.691 TimeOut: 5
14:55:02.698 TimeOut: 5
14:55:03.690 TimeOut: 5
14:55:04.690 TimeOut: 5
14:55:05.696 TimeOut: 5

El tener activado el temporizador de la consola del navegador nos permite apreciar también en que momento se ejecuta cada salida por consola.

Revisando esto podemos ver que la función setTimeout tarda más en lanzarse pro primera vez que lo que tarda el bucle en recorrerse entero, por lo que esta cuando accede a la variable i ya se encuentra con valor 5.

phpMyGuel
  • 14,074
  • 1
  • 20
  • 38
  • 1
    creo que la pregunta va a explicar mejor el singlethread , el tiempo de compilacion ejecuta el for y coloca en el stack el setTimeout ya que es una funcion asincrona – JackNavaRow Jan 09 '20 at 14:08
  • 1
    Realmente si que se trata de un problema de monohilo y de cuando se ejecutan en la pila otros hilos de ejecución fuera del principal. Gracias por el aporte @JackNavaRow – phpMyGuel Jan 09 '20 at 14:24
12

Lo mejor para entender lo que pasa es reproducir la ejecución manualmente:

for (var i = 0; i < 5; i++) { // durante 5 veces ...
  setTimeout(function() {     // ... colocamos en la cola de eventos por tiempo...
    console.log(i);           // ... una función que imprime el valor de i ...
  },i * 1000 );               // ... con un segundo entre cada función.
}

Y un segundo después, con i valiendo 5 (que es cuando el bucle ha terminado), se imprime su valor.

Y un segundo después, con i valiendo 5 (que es cuando el bucle ha terminado), se imprime su valor.

Y un segundo después, ... bueno, ya sabemos lo que pasa.

Esto ocurre porque se ha creado una clausura de función: i es una variable externa a la función anónima que se le ha pasado a setTimeout, por lo que se queda en memoria con el último valor existente.

¿Por qué se comparte i entre las 5 funciones?, bueno, hay que entender que el uso de var conlleva un comportamiento especial: El compilador "sube" la declaración al inicio del bloque:

//equivalente a tu código
var i;
for (i = 0; i < 5; i++) {
 setTimeout(function() { 
    console.log(i); 
 },i * 1000 );
}

En cambio, si usases let, en cada iteración estarías trabajando con una instancia distinta:

for (let i = 0; i < 5; i++) {
 //en cada iteración se genera una variable i con un valor distinto!
 setTimeout(function() { 
    console.log(Date(),'->',i); // es una variable distinta para cada función
 },i * 1000 );
}

Otra solución, en caso de que no puedas usar let (no funciona en IE10), es usar lo siguiente:

for (var i = 0; i < 5; i++) {

 setTimeout(function(param1, param2) { 
    console.log(param1, param2); 
 },i * 1000, 'Pasando 2 parámetros',i );
}

La función setTimeout permite definir los parámetros con los que será llamada la función callback:

setTimeout(funcion[, retraso, parametro1, parametro2, ...]);
Pablo Lozano
  • 45,934
  • 7
  • 48
  • 87
  • Me intriga saber (y nadie contesto) porque la ultima funcion no tarda 5 segundos en ejecutarse.. si el timeout de la ultima es 5000... – gbianchi Jan 09 '20 at 15:52
  • Tarda 5 segundos, pero quizá no te das cuenta porque la anterior tardó 4, la anterior 3... con lo que cada segundo que pasa tienes una escritura. He añadido un *timestamp* a lo que se muestra – Pablo Lozano Jan 09 '20 at 15:59
  • Me desdigo.. tenes razon.. se ve que el problema fue donde yo lo probe.. – gbianchi Jan 09 '20 at 16:02
11

Para responder debes saber dos cosas:

  • JavaScript es single thread, y como lo explica nuestro Gurú Trauma, Javascript ejecuta todo el archivo .js y las funciones asincronas la coloca en su cola de eventos; es decir que el codigo ejecutado en primera instancia es for (var i = 0; i < 5; i++){ } y en la cola de eventos de Javascript queda cinco veces setTimeout(function() { console.log('TimeOut: ' + i); },i * 1000 );

  • Al declarar la variable tipo var esta tiene otro tipo de alcance. por el cual el ultimo valor sera el #5, y al ejecutar el setTimeout desde la cola de eventos i ya tiene el valor # 5

for (var i = 0; i< 5; i++){
}
console.log(i);

Modificare el código un poco , para fines practicos puedas entender del porque 5

function ejecutatimeOut(){
    setTimeout(function() { 
      console.info(`TimeOut ${i}`);
      
   },i  * 1000 );
 
 }
var i = 0;

for (i = 0; i < 5; i++) {

}

console.log("valor de i=" , i); // la variable ya esta en 5
ejecutatimeOut()
ejecutatimeOut()
ejecutatimeOut()
ejecutatimeOut()
ejecutatimeOut()

La solución a esta pregunta te invito a leer esta respuesta:

¿Cómo ejecutar un timeout esperando el evento anterior?

JackNavaRow
  • 6,836
  • 5
  • 22
  • 49
4

Al declarar la variable con var, en el for se está sobrescribiendo su valor. Prueba con un let:

    for (let i = 0; i < 5; i++) {
     setTimeout(function() { 
        console.log(i); 
     },i * 1000 );
    }
Jesús
  • 1,543
  • 3
  • 10
  • 25
0

Se puede hacer una función que devuelva otra función.

Esta función recibe el argumento i, y se va transpasando a la función de adentro, aunque esa función, no recibe ningún argumento.

Por último, se le pasa el argumento para que esa función sea generada.

for (var i = 0; i < 5; i++) {
  setTimeout( // La i se transpasa hacia abajo
    (function(i){ // ----------+
      return function(){ //    ‖
         console.log(i); // <--+
      }
    })(i), // Aquí se le pasa el argumento.
    i * 1000 );
}