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

345

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

Lo intenté así:

alert('hi');

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

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

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
fuente

Respuestas:

749

La setTimeout()función no bloquea y regresará de inmediato. Por lo tanto, su bucle iterará muy rápidamente e iniciará disparadores de tiempo de espera de 3 segundos uno tras otro en rápida sucesión. Es por eso que sus primeras alertas aparecen después de 3 segundos, y todo el resto sigue en sucesión sin demora.

Es posible que desee 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

También podría compensarlo, utilizando una función de invocación automática, 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

Daniel Vassallo
fuente
27
¿El uso de la recursión para implementar esto no estaría sujeto a un desbordamiento de la pila? Si quisieras hacer un millón de iteraciones, ¿cuál sería una mejor manera de implementar esto? ¿Tal vez establecer Intervalo y luego borrarlo, como la solución de Abel a continuación?
Adam
77
@ Adam: Tengo entendido que, dado que setTimeout no bloquea, esto no es una reclusión: la ventana de la pila se cierra después de cada setTimeout y solo hay un setTimeout esperando para ejecutarse ... ¿Verdad?
Joe
3
¿Cómo funcionaría esto al iterar un objeto como un for inbucle?
vsync
1
@vsync ExaminarObject.keys()
Braden Best
1
@joey Estás confundiendo setTimeoutcon setInterval. Los tiempos de espera se destruyen implícitamente cuando se llama a la devolución de llamada.
cdhowie
72

Intenta algo como esto:

var i = 0, howManyTimes = 10;
function f() {
    alert( "hi" );
    i++;
    if( i < howManyTimes ){
        setTimeout( f, 3000 );
    }
}
f();
cji
fuente
69

Si usa ES6, puede usar letpara lograr esto:

for (let i=1; i<10; i++) {
    setTimeout( function timer(){
        alert("hello world");
    }, i*3000 );
}

Lo que lethace es declarar ipara cada iteración , no el ciclo. De esta manera, lo que se pasa setTimeoutes exactamente lo que queremos.

Saket Mehta
fuente
1
¡Gracias! No habría pensado en este método por mi cuenta. Alcance real del bloque. Imagina que ...
Sophia Gold
1
Creo que esto tiene los mismos problemas de asignación de memoria que la respuesta descrita en stackoverflow.com/a/3583795/1337392
Flame_Phoenix
1
@Flame_Phoenix ¿Qué problemas de asignación de memoria?
4castle
1
La llamada setTimeout calcula sincrónicamente el valor del i*3000argumento, dentro del bucle, y lo pasa setTimeoutpor valor. El uso de letes opcional y no está relacionado con la pregunta y la respuesta.
traktor53
@Flame_Phoenix mencionó que hay problemas en este código. Básicamente, en la primera pasada, crea un temporizador y luego repite el ciclo inmediatamente una y otra vez hasta que finalice el ciclo por la condición ( i < 10) para que tenga varios temporizadores trabajando en paralelo, lo que crea una asignación de memoria y es peor en una mayor cantidad de iteraciones.
XCanG
63

Dado que ES7 hay una mejor manera de esperar un bucle:

// Returns a Promise that resolves after "ms" Milliseconds
function timer(ms) {
 return 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 awaitparte, 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. Eso es bastante útil ya que puede 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)); }

Referencia sobre 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
fuente
Funciona bien para mí. Solo quiero preguntar que si quiero romper el bucle, ¿cómo puedo hacerlo cuando estoy esperando?
Sachin Shah
@sachin break;tal vez?
Jonas Wilms
Gracias por esta solucion. Es bueno usar todas las estructuras de control existentes y no es necesario inventar continuaciones.
Gus
¿Esto solo crearía varios temporizadores y se resolverían en diferentes momentos en lugar de en secuencia?
David Yell
@david um nope? ¿Realmente has ejecutado el código?
Jonas Wilms
24

Otra forma es multiplicar el tiempo de espera, pero tenga en cuenta que esto no es como dormir . El código después del bucle se ejecutará inmediatamente, solo se aplaza la ejecución de la función de devolución de llamada.

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

El primer tiempo de espera se establecerá en 3000 * 1 , el segundo 3000 * 2y así sucesivamente.

Felix Kling
fuente
2
Vale la pena señalar que no puede usar de manera confiable startdentro de su función usando este método.
DBS
2
Mala práctica: asignación innecesaria de memoria.
Alexander Trakhimenok
Vota por la creatividad, pero es una maldita mala práctica. :)
Salivan
2
¿Por qué es una mala práctica y por qué tiene problemas de asignación de memoria? ¿Esta respuesta sufre los mismos problemas? stackoverflow.com/a/36018502/1337392
Flame_Phoenix
1
@Flame_Phoenix es una mala práctica porque el programa mantendrá un temporizador para cada ciclo, con todos los temporizadores ejecutándose al mismo tiempo. Entonces, si hay 1000 iteraciones, habrá 1000 temporizadores ejecutándose al mismo tiempo al principio.
Joakim
16

Esto funcionará

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

Pruebe este violín: https://jsfiddle.net/wgdx8zqq/

Gsvp Nagaraju
fuente
1
Sin embargo, esto activa todas las llamadas de tiempo de espera casi al mismo tiempo
Eddie
Lo único que digo es que lo he descifrado de esta manera, lo utilicé $.Deferredpero fue un escenario diferente para dejarlo funcionar, ¡aprobado!
ArifMustafa
15

Creo que necesitas algo como esto:

var TimedQueue = function(defaultDelay){
    this.queue = [];
    this.index = 0;
    this.defaultDelay = defaultDelay || 3000;
};

TimedQueue.prototype = {
    add: function(fn, delay){
        this.queue.push({
            fn: fn,
            delay: delay
        });
    },
    run: function(index){
        (index || index === 0) && (this.index = index);
        this.next();
    },
    next: function(){
        var self = this
        , i = this.index++
        , at = this.queue[i]
        , next = this.queue[this.index]
        if(!at) return;
        at.fn();
        next && setTimeout(function(){
            self.next();
        }, next.delay||this.defaultDelay);
    },
    reset: function(){
        this.index = 0;
    }
}

Código de prueba:

var now = +new Date();

var x = new TimedQueue(2000);

x.add(function(){
    console.log('hey');
    console.log(+new Date() - now);
});
x.add(function(){
    console.log('ho');
    console.log(+new Date() - now);
}, 3000);
x.add(function(){
    console.log('bye');
    console.log(+new Date() - now);
});

x.run();

Nota: el uso de alertas detiene la ejecución de JavaScript hasta que cierre la alerta. Puede ser más código del que solicitó, pero esta es una solución robusta y reutilizable.

BGerrissen
fuente
15

Probablemente lo usaría setInteval. Me gusta esto,

var period = 1000; // ms
var endTime = 10000;  // ms
var counter = 0;
var sleepyAlert = setInterval(function(){
    alert('Hello');
    if(counter === endTime){
       clearInterval(sleepyAlert);
    }
    counter += period;
}, period);
Abel Terefe
fuente
3
SetTimeout es mucho mejor que settinterval. google y lo sabrá
Airy
14
Lo busqué en Google un poco y no encontré nada. ¿Por qué setInterval es malo? Puedes darnos un enlace ? o un ejemplo? Gracias
Marcs
Supongo que el punto fue que SetInterval()sigue generando 'hilos' incluso en caso de algún error o bloqueo.
Mateen Ulhaq
8

En ES6 (ECMAScript 2015) puede iterar con retraso con generador e intervalo.

Los generadores, una nueva característica de ECMAScript 6, son funciones que se pueden pausar y reanudar. Llamar a genFunc no lo ejecuta. En cambio, devuelve el llamado objeto generador que nos permite controlar la ejecución de genFunc. genFunc () se suspende inicialmente al comienzo de su cuerpo. El método genObj.next () continúa la ejecución de genFunc, hasta el próximo rendimiento. (Explorando ES6)


Ejemplo de código:

let arr = [1, 2, 3, 'b'];
let genObj = genFunc();

let val = genObj.next();
console.log(val.value);

let interval = setInterval(() => {
  val = genObj.next();
  
  if (val.done) {
    clearInterval(interval);
  } else {
    console.log(val.value);
  }
}, 1000);

function* genFunc() {
  for(let item of arr) {
    yield item;
  }
}

Entonces, si está utilizando ES6, esa es la forma más elegante de lograr un bucle con retraso (en mi opinión).

Itay Radotzki
fuente
4

Puede usar el operador de intervalo RxJS . El intervalo emite un número entero cada x número de segundos, y tomar es especificar el número de veces que tiene que emitir números

Rx.Observable
  .interval(1000)
  .take(10)
  .subscribe((x) => console.log(x))
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.1.0/rx.lite.min.js"></script>

Vlad Bezden
fuente
4

Solo pensé en publicar mis dos centavos aquí también. Esta función ejecuta un ciclo iterativo con un retraso. Ver este jsfiddle . La función es la siguiente:

function timeout(range, time, callback){
    var i = range[0];                
    callback(i);
    Loop();
    function Loop(){
        setTimeout(function(){
            i++;
            if (i<range[1]){
                callback(i);
                Loop();
            }
        }, time*1000)
    } 
}

Por ejemplo:

//This function prints the loop number every second
timeout([0, 5], 1, function(i){
    console.log(i);
});

Sería equivalente a:

//This function prints the loop number instantly
for (var i = 0; i<5; i++){
    console.log(i);
}
D Slee
fuente
4

Hago esto con Bluebird's Promise.delayy recursividad.

function myLoop(i) {
  return Promise.delay(1000)
    .then(function() {
      if (i > 0) {
        alert('hello');
        return myLoop(i -= 1);
      }
    });
}

myLoop(3);
<script src="//cdnjs.cloudflare.com/ajax/libs/bluebird/2.9.4/bluebird.min.js"></script>

Dave Bryand
fuente
2

En ES6 puede hacer lo siguiente:

 for (let i = 0; i <= 10; i++){       
     setTimeout(function () {   
        console.log(i);
     }, i*3000)
 }

En ES5 puedes hacer lo siguiente:

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

La razón es letque le permite declarar variables que están limitadas al alcance de una declaración de bloque o expresión en la que se utiliza, a diferencia de la varpalabra clave, que define una variable global o localmente para una función completa, independientemente del alcance del bloque.

Tabish
fuente
1

Una versión modificada de la respuesta de Daniel Vassallo, con variables extraídas en parámetros para hacer que la función sea más reutilizable:

Primero definamos algunas variables esenciales:

var startIndex = 0;
var data = [1, 2, 3];
var timeout = 3000;

A continuación, debe definir la función que desea ejecutar. Esto se pasará i, el índice actual del bucle y la longitud del bucle, en caso de que lo necesite:

function functionToRun(i, length) {
    alert(data[i]);
}

Versión autoejecutable

(function forWithDelay(i, length, fn, delay) {
   setTimeout(function () {
      fn(i, length);
      i++;
      if (i < length) {
         forWithDelay(i, length, fn, delay); 
      }
  }, delay);
})(startIndex, data.length, functionToRun, timeout);

Versión funcional

function forWithDelay(i, length, fn, delay) {
   setTimeout(function () {
      fn(i, length);
      i++;
      if (i < length) {
         forWithDelay(i, length, fn, delay); 
      }
  }, delay);
}

forWithDelay(startIndex, data.length, functionToRun, timeout); // Lets run it
Jasdeep Khalsa
fuente
agradable y cómo paso datos a la función sin una variable global
Sundara Prabu
1
   let counter =1;
   for(let item in items) {
        counter++;
        setTimeout(()=>{
          //your code
        },counter*5000); //5Sec delay between each iteration
    }
Ali Azhar
fuente
1

Hazlo tu:

console.log('hi')
let start = 1
setTimeout(function(){
  let interval = setInterval(function(){
    if(start == 10) clearInterval(interval)
    start++
    console.log('hello')
  }, 3000)
}, 3000)
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

Nguyen Ba Danh - FAIC HN
fuente
mejor usar los registros de la consola en lugar de las alertas, no fue muy divertido cerrar las alertas durante medio minuto;)
Hendry el
Si. ¡Veo! Pero la solicitud está alerta ... huz
Nguyen Ba Danh - FAIC HN
¿Por qué importar jQuery?
Elias Soares
Lo siento ... es innecesario ... je. No sé el contenido de la publicación ... esto primero.
Nguyen Ba Danh - FAIC HN
0
/* 
  Use Recursive  and setTimeout 
  call below function will run loop loopFunctionNeedCheck until 
  conditionCheckAfterRunFn = true, if conditionCheckAfterRunFn == false : delay 
  reRunAfterMs miliseconds and continue loop
  tested code, thanks
*/

function functionRepeatUntilConditionTrue(reRunAfterMs, conditionCheckAfterRunFn,
 loopFunctionNeedCheck) {
    loopFunctionNeedCheck();
    var result = conditionCheckAfterRunFn();
    //check after run
    if (!result) {
        setTimeout(function () {
            functionRepeatUntilConditionTrue(reRunAfterMs, conditionCheckAfterRunFn, loopFunctionNeedCheck)
        }, reRunAfterMs);
    }
    else  console.log("completed, thanks");    
            //if you need call a function after completed add code call callback in here
}

//passing-parameters-to-a-callback-function
// From Prototype.js 
if (!Function.prototype.bind) { // check if native implementation available
    Function.prototype.bind = function () {
        var fn = this, args = Array.prototype.slice.call(arguments),
            object = args.shift();
        return function () {
            return fn.apply(object,
              args.concat(Array.prototype.slice.call(arguments)));
        };
    };
}

//test code: 
var result = 0; 
console.log("---> init result is " + result);
var functionNeedRun = function (step) {           
   result+=step;    
       console.log("current result is " + result);  
}
var checkResultFunction = function () {
    return result==100;
}  

//call this function will run loop functionNeedRun and delay 500 miliseconds until result=100    
functionRepeatUntilConditionTrue(500, checkResultFunction , functionNeedRun.bind(null, 5));

//result log from console:
/*
---> init result is 0
current result is 5
undefined
current result is 10
current result is 15
current result is 20
current result is 25
current result is 30
current result is 35
current result is 40
current result is 45
current result is 50
current result is 55
current result is 60
current result is 65
current result is 70
current result is 75
current result is 80
current result is 85
current result is 90
current result is 95
current result is 100
completed, thanks
*/
usuario2913925
fuente
77
Los nombres de sus funciones son horrendos, esa es la razón principal por la que este código es tan difícil de leer.
Mark Walters
0

Así es como creé un bucle infinito con un retraso que se rompe en una determinada condición:

  // Now continuously check the app status until it's completed, 
  // failed or times out. The isFinished() will throw exception if
  // there is a failure.
  while (true) {
    let status = await this.api.getStatus(appId);
    if (isFinished(status)) {
      break;
    } else {
      // Delay before running the next loop iteration:
      await new Promise(resolve => setTimeout(resolve, 3000));
    }
  }

La clave aquí es crear una nueva Promesa que se resuelva por tiempo de espera y esperar su resolución.

Obviamente necesita soporte asíncrono / espera para eso. Funciona en el Nodo 8.


fuente
0

para uso común "olvide los bucles normales" y use esta combinación de "setInterval" que incluye "setTimeOut": así (de mis tareas reales).

        function iAsk(lvl){
            var i=0;
            var intr =setInterval(function(){ // start the loop 
                i++; // increment it
                if(i>lvl){ // check if the end round reached.
                    clearInterval(intr);
                    return;
                }
                setTimeout(function(){
                    $(".imag").prop("src",pPng); // do first bla bla bla after 50 millisecond
                },50);
                setTimeout(function(){
                     // do another bla bla bla after 100 millisecond.
                    seq[i-1]=(Math.ceil(Math.random()*4)).toString();
                    $("#hh").after('<br>'+i + ' : rand= '+(Math.ceil(Math.random()*4)).toString()+' > '+seq[i-1]);
                    $("#d"+seq[i-1]).prop("src",pGif);
                    var d =document.getElementById('aud');
                    d.play();                   
                },100);
                setTimeout(function(){
                    // keep adding bla bla bla till you done :)
                    $("#d"+seq[i-1]).prop("src",pPng);
                },900);
            },1000); // loop waiting time must be >= 900 (biggest timeOut for inside actions)
        }

PD: Comprenda que el comportamiento real de (setTimeOut): todos comenzarán al mismo tiempo "los tres bla bla bla comenzarán la cuenta atrás en el mismo momento", así que haga un tiempo de espera diferente para organizar la ejecución.

PS 2: el ejemplo para el bucle de tiempo, pero para los bucles de reacción puede usar eventos, promete async esperar.

Mohamed Abulnasr
fuente
0

<!DOCTYPE html>
<html>
<body>

<button onclick="myFunction()">Try it</button>

<p id="demo"></p>

<script>
function myFunction() {
    for(var i=0; i<5; i++) {
    	var sno = i+1;
       	(function myLoop (i) {          
             setTimeout(function () {   
             	alert(i); // Do your function here 
             }, 1000*i);
        })(sno);
    }
}
</script>

</body>
</html>

Boginaathan M
fuente
1
Siempre proporcione al menos una breve descripción de sus fragmentos de código, al menos para que otros se aseguren de responder la pregunta.
Hexfire el
1
Código únicas respuestas enviaban anima, ya que no proporcionan mucha información para los futuros lectores Sírvanse proporcionar una explicación a lo que ha escrito
WhatsThePoint
0

Que yo sepa, el setTimeout función se llama asincrónicamente. Lo que puede hacer es envolver todo el ciclo dentro de una función asíncrona y esperar a Promiseque contenga setTimeout como se muestra:

var looper = async function () {
  for (var start = 1; start < 10; start++) {
    await new Promise(function (resolve, reject) {
      setTimeout(function () {
        console.log("iteration: " + start.toString());
        resolve(true);
      }, 1000);
    });
  }
  return true;
}

Y luego llamas ejecutarlo así:

looper().then(function(){
  console.log("DONE!")
});

Tómese un tiempo para comprender la programación asincrónica.

Questionare232
fuente
0

Solo prueba esto

 var arr = ['A','B','C'];
 (function customLoop (arr, i) {
    setTimeout(function () {
    // Do here what you want to do.......
    console.log(arr[i]);
    if (--i) {                
      customLoop(arr, i); 
    }
  }, 2000);
})(arr, arr.length);

Resultado

A // after 2s
B // after 2s
C // after 2s
Shirantha Madusanka
fuente
-1

Aquí hay una función que uso para recorrer una matriz:

function loopOnArrayWithDelay(theArray, delayAmount, i, theFunction, onComplete){

    if (i < theArray.length && typeof delayAmount == 'number'){

        console.log("i "+i);

        theFunction(theArray[i], i);

        setTimeout(function(){

            loopOnArrayWithDelay(theArray, delayAmount, (i+1), theFunction, onComplete)}, delayAmount);
    }else{

        onComplete(i);
    }
}

Lo usas así:

loopOnArrayWithDelay(YourArray, 1000, 0, function(e, i){
    //Do something with item
}, function(i){
    //Do something once loop has completed
}
PJeremyMalouf
fuente
-1

Este script funciona para la mayoría de las cosas.

function timer(start) {
    setTimeout(function () { //The timer
        alert('hello');
    }, start*3000); //needs the "start*" or else all the timers will run at 3000ms
}

for(var start = 1; start < 10; start++) {
    timer(start);
}
Jaketr00
fuente
-1

Prueba esto...

var icount=0;
for (let i in items) {
   icount=icount+1000;
   new beginCount(items[i],icount);
}

function beginCount(item,icount){
  setTimeout(function () {

   new actualFunction(item,icount);

 }, icount);
}

function actualFunction(item,icount){
  //...runs ever 1 second
 console.log(icount);
}
PHILL BOOTH
fuente
-1

Implementación simple de mostrar un fragmento de texto cada dos segundos mientras se ejecuta el bucle.

for (var i = 0; i < foo.length; i++) {
   setInterval(function(){ 
     console.log("I will appear every 2 seconds"); 
   }, 2000);
  break;
};
squeekyDave
fuente
-3

Prueba esto

//the code will execute in 1 3 5 7 9 seconds later
function exec(){
  for(var i=0;i<5;i++){
   setTimeout(function(){
     console.log(new Date());   //It's you code
   },(i+i+1)*1000);
  }
}
Steve Jiang
fuente