¿Cómo agrego un retraso en un bucle de JavaScript?

Resuelto olidev asked hace 14 años • 34 respuestas

Me gustaría agregar un retraso/suspensión dentro de un whilebucle:

Lo probé así:

alert('hi');

for(var start = 1; start < 10; start++) {
  setTimeout(function () {
    alert('hello');
  }, 3000);
}

Solo el primer escenario es cierto: después de mostrar alert('hi'), esperará 3 segundos y luego alert('hello')se mostrará, pero luego alert('hello')se repetirá de forma constante.

Lo que me gustaría es que después alert('hello')se muestre 3 segundos después, alert('hi')luego debe esperar 3 segundos por segunda vez alert('hello')y así sucesivamente.

olidev avatar Aug 27 '10 18:08 olidev
Aceptado

La setTimeout()función no es bloqueante y volverá inmediatamente. Por lo tanto, su bucle se repetirá muy rápidamente e iniciará activadores de tiempo de espera de 3 segundos uno tras otro en rápida sucesión. Es por eso que las primeras alertas aparecen después de 3 segundos y todas las demás siguen sucesivamente sin demora.

Quizás quieras usar algo como esto en su lugar:

var i = 1;                  //  set your counter to 1

function myLoop() {         //  create a loop function
  setTimeout(function() {   //  call a 3s setTimeout when the loop is called
    console.log('hello');   //  your code here
    i++;                    //  increment the counter
    if (i < 10) {           //  if the counter < 10, call the loop function
      myLoop();             //  ..  again which will trigger another 
    }                       //  ..  setTimeout()
  }, 3000)
}

myLoop();                   //  start the loop
Expandir fragmento

También puedes mejorarlo usando una función de autoinvocación y pasando el número de iteraciones como argumento:

(function myLoop(i) {
  setTimeout(function() {
    console.log('hello'); //  your code here                
    if (--i) myLoop(i);   //  decrement i and call myLoop again if i > 0
  }, 3000)
})(10);                   //  pass the number of iterations as an argument
Expandir fragmento

Daniel Vassallo avatar Aug 27 '2010 11:08 Daniel Vassallo

Desde ES7 hay una mejor manera de esperar un bucle:

// Returns a Promise that resolves after "ms" Milliseconds
const timer = ms => new Promise(res => setTimeout(res, ms))

async function load () { // We need to wrap the loop into an async function for this to work
  for (var i = 0; i < 3; i++) {
    console.log(i);
    await timer(3000); // then the created Promise can be awaited
  }
}

load();

Cuando el motor llega a la awaitpieza, establece un tiempo de espera y detiene la ejecución delasync function . Luego, cuando se completa el tiempo de espera, la ejecución continúa en ese punto. Esto es bastante útil ya que puedes retrasar (1) bucles anidados, (2) condicionalmente, (3) funciones anidadas:

async function task(i) { // 3
  await timer(1000);
  console.log(`Task ${i} done!`);
}

async function main() {
  for(let i = 0; i < 100; i+= 10) {
    for(let j = 0; j < 10; j++) { // 1
      if(j % 2) { // 2
        await task(i + j);
      }
    }
  }
}
    
main();

function timer(ms) { return new Promise(res => setTimeout(res, ms)); }
Expandir fragmento

Referencia en MDN

Si bien ES7 ahora es compatible con NodeJS y los navegadores modernos, es posible que desee transpilarlo con BabelJS para que se ejecute en todas partes.

Jonas Wilms avatar Jun 10 '2017 18:06 Jonas Wilms

Si usa ES6, puede usar un bucle for para lograr esto:

for (let i = 1; i < 10; i++) {
  setTimeout(function timer() {
    console.log("hello world");
  }, i * 3000);
}
Expandir fragmento

Declara ipara cada iteración , lo que significa que el tiempo de espera es el que era antes de + 1000. De esta manera, lo que se pasa setTimeoutes exactamente lo que queremos.

Saket Mehta avatar Mar 15 '2016 17:03 Saket Mehta