Come faccio ad aggiungere un ritardo in un ciclo JavaScript?


346

Vorrei aggiungere un ritardo / sospensione all'interno di a while loop:

L'ho provato in questo modo:

alert('hi');

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

È vero solo il primo scenario: dopo averlo mostrato alert('hi'), attenderà 3 secondi quindi alert('hello')verrà visualizzato ma poi alert('hello')sarà ripetutamente in modo continuo.

Quello che vorrei è che dopo alert('hello')viene mostrato 3 secondi dopo, alert('hi')quindi deve attendere 3 secondi per la seconda volta alert('hello')e così via.

Risposte:


750

La setTimeout()funzione non è bloccante e tornerà immediatamente. Pertanto, il ciclo eseguirà un'iterazione molto rapida e verrà avviato il timeout di 3 secondi che si innesca uno dopo l'altro in rapida successione. Questo è il motivo per cui i primi avvisi vengono visualizzati dopo 3 secondi e tutto il resto segue in successione senza alcun ritardo.

Potresti voler usare qualcosa del genere invece:

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

Puoi anche migliorarlo, usando una funzione auto-invocante, passando il numero di iterazioni come argomento:

(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


27
L'uso della ricorsione per implementare questo non sarebbe soggetto a un overflow dello stack alla fine? Se volessi fare un milione di iterazioni, quale sarebbe un modo migliore per implementarlo? Forse setInterval e poi cancellalo, come la soluzione di Abel di seguito?
Adam,

7
@Adam: la mia comprensione è che, dato che setTimeout non è bloccante, questa non è una ricaduta: la finestra dello stack si chiude dopo ogni setTimeout e c'è solo un setTimeout in attesa di essere eseguito ... Giusto?
Joe,

3
Come funzionerebbe quando si itera un oggetto come un for inciclo?
vsync,

1
@vsync Look intoObject.keys()
Braden Best

1
@joey Si confonde setTimeoutcon setInterval. I timeout vengono implicitamente distrutti quando viene chiamato il callback.
cdhowie

72

Prova qualcosa del genere:

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

69

Se si utilizza ES6, è possibile utilizzare letper raggiungere questo obiettivo:

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

Quello che letfa è dichiarare iper ogni iterazione , non il ciclo. In questo modo, ciò che viene passato setTimeoutè esattamente ciò che vogliamo.


1
Grazie! Non avrei pensato a questo metodo da solo. Scoping del blocco effettivo. Immagina che ...
Sophia Gold,

1
Credo che questo ha gli stessi problemi di allocazione di memoria come la risposta descritto nella stackoverflow.com/a/3583795/1337392
Flame_Phoenix

1
@Flame_Phoenix Quali problemi di allocazione della memoria?
4castle,

1
La chiamata setTimeout calcola in modo sincrono il valore i*3000dell'argomento, all'interno del ciclo, e lo passa per setTimeoutvalore. L'uso di letè facoltativo e non correlato alla domanda e alla risposta.
traktor53,

@Flame_Phoenix ha menzionato che ci sono problemi in questo codice. Fondamentalmente al primo passaggio crei il timer, quindi ripeti immediatamente il ciclo ripetutamente fino alla fine del ciclo in base alla condizione ( i < 10), quindi avrai più timer in parallelo che creano allocazione di memoria ed è peggio su un maggior numero di iterazioni.
XCanG,

63

Da ES7 esiste un modo migliore per attendere un loop:

// 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();

Quando il motore raggiunge la awaitparte, imposta un timeout e interrompe l'esecuzione diasync function . Quindi al termine del timeout, l'esecuzione continua a quel punto. È abbastanza utile in quanto puoi ritardare (1) loop nidificati, (2) in modo condizionale, (3) funzioni nidificate:

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)); }

Riferimento su MDN

Mentre ES7 è ora supportato da NodeJS e dai browser moderni, potresti volerlo trasferire con BabelJS in modo che funzioni ovunque.


Funziona bene per me. Voglio solo chiedere che se voglio interrompere il loop, come posso farlo quando utilizzo wait?
Sachin Shah,

@sachin break;forse?
Jonas Wilms,

Grazie per questa soluzione È bello utilizzare tutte le strutture di controllo esistenti e non è necessario inventare continuazioni.
Gus,

Ciò creerebbe comunque vari timer e si risolverebbero in momenti diversi anziché in sequenza?
David Yell,

@JonasWilms Sembra che mi sia sfuggito il pulsante "Esegui frammento": facepalm:
David Yell,

24

Un altro modo è moltiplicare il tempo di timeout, ma nota che non è come dormire . Il codice dopo il ciclo verrà eseguito immediatamente, verrà rimandata solo l'esecuzione della funzione di richiamata.

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

Il primo timeout verrà impostato su 3000 * 1, il secondo su 3000 * 2e così via.


2
Vale la pena sottolineare che non è possibile utilizzare in modo affidabile startall'interno della funzione utilizzando questo metodo.
DBS,

2
Cattiva pratica - allocazione di memoria non necessaria.
Alexander Trakhimenok,

Voto per la creatività, ma è dannatamente cattiva pratica. :)
Salivan,

2
Perché è una cattiva pratica e perché ha problemi di allocazione della memoria? Questa risposta presenta gli stessi problemi? stackoverflow.com/a/36018502/1337392
Flame_Phoenix

1
@Flame_Phoenix è una cattiva pratica perché il programma manterrà un timer per ogni loop, con tutti i timer in esecuzione contemporaneamente. Quindi se ci sono 1000 iterazioni, all'inizio ci saranno 1000 timer in esecuzione contemporaneamente.
Joakim,

16

Questo funzionerà

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

Prova questo violino: https://jsfiddle.net/wgdx8zqq/


1
Questo innesca tutte le chiamate di timeout quasi nello stesso momento
Eddie,

l'unica cosa che dico, mi sono incrinato in questo modo, usato $.Deferredma c'erano alcuni scenari diversi per farlo funzionare, i pollici per te ..!
ArifMustafa,

15

Penso che tu abbia bisogno di qualcosa del genere:

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;
    }
}

Codice di prova:

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: l'utilizzo degli avvisi blocca l'esecuzione di javascript fino alla chiusura dell'avviso. Potrebbe essere più codice di quanto richiesto, ma questa è una soluzione riutilizzabile solida.


15

Probabilmente userei setInteval. Come questo,

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);

3
SetTimeout è molto meglio di settinterval. cercalo su google e lo saprai
Airy,

14
Lo cerco un po 'in giro e non ho trovato nulla, perché setInterval è male? Puoi darci un link? o un esempio? Grazie
Marcs

Immagino che il punto fosse che SetInterval()continua a generare "thread" anche in caso di errore o blocco.
Mateen Ulhaq,

8

In ES6 (ECMAScript 2015) puoi iterare con ritardo con generatore e intervallo.

I generatori, una nuova funzionalità di ECMAScript 6, sono funzioni che possono essere messe in pausa e riprese. Chiamare genFunc non lo esegue. Al contrario, restituisce un cosiddetto oggetto generatore che ci consente di controllare l'esecuzione di genFunc. genFunc () è inizialmente sospeso all'inizio del suo corpo. Il metodo genObj.next () continua l'esecuzione di genFunc, fino alla resa successiva. (Esplorando ES6)


Esempio di codice:

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;
  }
}

Quindi, se stai usando ES6, questo è il modo più elegante per ottenere un loop con ritardo (a mio avviso).


4

È possibile utilizzare l' operatore di intervallo RxJS . L'intervallo emette un numero intero ogni x numero di secondi e take indica il numero di volte in cui deve emettere numeri

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>


4

Ho pensato di pubblicare anche qui i miei due centesimi. Questa funzione esegue un ciclo iterativo con un ritardo. Vedi questo jsfiddle . La funzione è la seguente:

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)
    } 
}

Per esempio:

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

Sarebbe equivalente a:

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

4

Lo faccio con Bluebird Promise.delaye la ricorsione.

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>


2

In ES6 puoi fare come segue:

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

In ES5 puoi fare come:

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

Il motivo è che letconsente di dichiarare variabili limitate a un ambito di un'istruzione di blocco o espressione su cui viene utilizzato, a differenza della varparola chiave, che definisce una variabile a livello globale o localmente a un'intera funzione indipendentemente dall'ambito del blocco.


1

Una versione modificata della risposta di Daniel Vassallo, con variabili estratte in parametri per rendere più riutilizzabile la funzione:

Per prima cosa definiamo alcune variabili essenziali:

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

Successivamente è necessario definire la funzione che si desidera eseguire. Questo verrà superato i, l'indice corrente del loop e la lunghezza del loop, nel caso in cui ne abbiate bisogno:

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

Versione autoeseguente

(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);

Versione funzionale

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

bello e come posso passare i dati alla funzione senza una variabile globale
Sundara Prabu

1
   let counter =1;
   for(let item in items) {
        counter++;
        setTimeout(()=>{
          //your code
        },counter*5000); //5Sec delay between each iteration
    }

1

Lo fai:

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>


meglio usare i registri della console invece degli avvisi, non è stato molto divertente chiudere gli avvisi per mezzo minuto;)
Hendry

Si. Vedo! Ma la richiesta è in allerta ... huz
Nguyen Ba Danh - FAIC HN,

Perché importare jQuery?
Elias Soares,

Scusa ... non è necessario .. eh. Non conosco i contenuti dei post ... questo prima.
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
*/

7
I nomi delle tue funzioni sono orrendi, questa è la ragione principale per cui questo codice è così difficile da leggere.
Mark Walters,

0

Ecco come ho creato un ciclo infinito con un ritardo che si interrompe a una determinata condizione:

  // 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 chiave qui è creare una nuova promessa che si risolve con il timeout e attendere la sua risoluzione.

Ovviamente hai bisogno di supporto asincrono / attendi per quello. Funziona nel nodo 8.


0

per uso comune "dimentica i normali cicli" e usa questa combinazione di "setInterval" include "setTimeOut" s: in questo modo (dai miei compiti reali).

        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)
        }

PS: Comprendi che il vero comportamento di (setTimeOut): tutti inizieranno nello stesso momento "i tre bla bla bla inizieranno il conto alla rovescia nello stesso momento" quindi fai un timeout diverso per organizzare l'esecuzione.

PS 2: l'esempio per il loop di temporizzazione, ma per i loop di reazione puoi usare gli eventi, prometti che async ti aspetta ..


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>


1
Fornisci sempre almeno una breve descrizione dei frammenti di codice, almeno affinché gli altri siano sicuri di rispondere alla domanda.
Hexfire

1
Le risposte al codice non sono incoraggiate, in quanto non forniscono molte informazioni ai lettori futuri, si prega di fornire alcune spiegazioni a ciò che è stato scritto
WhatsThePoint

0

Per quanto ne sappia, la setTimeoutfunzione viene chiamata in modo asincrono. Quello che puoi fare è avvolgere l'intero ciclo all'interno di una funzione asincrona e attendere un Promiseche contenga setTimeout come mostrato:

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;
}

E poi lo chiami corri in questo modo:

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

Ti preghiamo di dedicare un po 'di tempo per capire bene la programmazione asincrona.


0

Prova questo

 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);

Risultato

A // after 2s
B // after 2s
C // after 2s

-1

Ecco una funzione che utilizzo per eseguire il looping su un array:

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 usi in questo modo:

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

-1

Questo script funziona per la maggior parte delle cose

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);
}

-1

Prova questo...

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);
}

-1

Implementazione semplice di mostrare un pezzo di testo ogni due secondi per tutto il ciclo di esecuzione.

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

-3

Prova questo

//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);
  }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.