Wie füge ich einer JavaScript-Schleife eine Verzögerung hinzu?


345

Ich möchte eine Verzögerung / Schlaf in einem hinzufügen while Schleife :

Ich habe es so versucht:

alert('hi');

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

Nur das erste Szenario ist wahr: Nach dem Anzeigen wird es alert('hi')3 Sekunden warten, dann alert('hello')wird es angezeigt, aber dann alert('hello')wird es ständig wiederholt.

Was ich möchte ist, dass nach alert('hello')3 Sekunden angezeigt wird, alert('hi')dann muss es zum zweiten Mal 3 Sekunden warten alert('hello')und so weiter.

Antworten:


749

Die setTimeout()Funktion ist nicht blockierend und kehrt sofort zurück. Daher iteriert Ihre Schleife sehr schnell und löst schnell hintereinander 3-Sekunden-Timeout-Trigger aus. Aus diesem Grund werden Ihre ersten Warnungen nach 3 Sekunden angezeigt, und alle anderen werden ohne Verzögerung nacheinander angezeigt.

Möglicherweise möchten Sie stattdessen Folgendes verwenden:

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

Sie können es auch verbessern, indem Sie eine selbstaufrufende Funktion verwenden und die Anzahl der Iterationen als Argument übergeben:

(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


26
Würde die Verwendung von Rekursion zur Implementierung nicht irgendwann einem Stapelüberlauf unterliegen? Wenn Sie eine Million Iterationen durchführen möchten, was wäre ein besserer Weg, dies zu implementieren? Vielleicht setInterval und dann löschen, wie Abels Lösung unten?
Adam

7
@Adam: Mein Verständnis ist, dass, da setTimeout nicht blockiert, dies keine Reklusion ist - das Stapelfenster wird nach jedem setTimeout geschlossen und es gibt immer nur ein setTimeout, das auf die Ausführung wartet ... Richtig?
Joe

3
Wie würde dies funktionieren, wenn ein Objekt wie eine for inSchleife iteriert wird?
vsync

1
@vsync Look inObject.keys()
Braden Best

1
@joey Sie verwechseln setTimeoutmit setInterval. Zeitüberschreitungen werden implizit zerstört, wenn der Rückruf aufgerufen wird.
CDOWIE

72

Versuchen Sie so etwas:

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

69

Wenn Sie ES6 verwenden, können Sie Folgendes verwenden let:

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

Was letbedeutet, ist ifür jede Iteration zu deklarieren , nicht für die Schleife. Auf diese Weise setTimeoutwird genau das weitergegeben, was wir wollen.


1
Danken! Ich hätte nicht alleine an diese Methode gedacht. Tatsächliches Block-Scoping. Stellen Sie sich vor, dass ...
Sophia Gold

1
Ich glaube, dies hat die gleichen Speicherzuordnungsprobleme wie die Antwort in stackoverflow.com/a/3583795/1337392
Flame_Phoenix

1
@Flame_Phoenix Welche Speicherzuordnungsprobleme?
4castle

1
Der Aufruf von setTimeout berechnet synchron den Wert des i*3000Arguments innerhalb der Schleife und übergibt ihn als setTimeoutWert an. Die Verwendung von letist optional und steht in keinem Zusammenhang mit der Frage und Antwort.
traktor53

@Flame_Phoenix erwähnte, dass es Probleme in diesem Code gibt. Grundsätzlich erstellen Sie beim ersten Durchlauf einen Timer und wiederholen die Schleife dann sofort immer wieder, bis die Schleife durch Bedingung ( i < 10) endet, sodass mehrere Timer parallel arbeiten, wodurch eine Speicherzuordnung erstellt wird, und dies ist bei einer größeren Anzahl von Iterationen schlechter.
XCanG

63

Seit ES7 gibt es einen besseren Weg zu auf eine Schleife warten :

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

Wenn der Motor das awaitTeil erreicht, setzt er eine Zeitüberschreitung und stoppt die Ausführung desasync function . Wenn das Zeitlimit abgelaufen ist, wird die Ausführung an diesem Punkt fortgesetzt. Dies ist sehr nützlich, da Sie (1) verschachtelte Schleifen, (2) bedingt, (3) verschachtelte Funktionen verzögern können:

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

Referenz zu MDN

Während ES7 jetzt von NodeJS und modernen Browsern unterstützt wird, möchten Sie es möglicherweise mit BabelJS transpilieren, damit es überall ausgeführt wird.


Es funktioniert gut für mich. Ich möchte nur fragen, ob ich, wenn ich die Schleife unterbrechen möchte, dies tun kann, wenn ich "Warten" verwende.
Sachin Shah

@sachin break;vielleicht?
Jonas Wilms

Danke für diese Lösung. Es ist schön, alle vorhandenen Kontrollstrukturen zu verwenden und keine Fortsetzungen zu erfinden.
Gus

Dies würde immer noch nur verschiedene Timer erzeugen und sie würden zu unterschiedlichen Zeiten und nicht nacheinander aufgelöst?
David Yell

@ David um nein? Haben Sie den Code tatsächlich ausgeführt?
Jonas Wilms

24

Eine andere Möglichkeit besteht darin, die Zeit bis zum Timeout zu multiplizieren. Beachten Sie jedoch, dass dies nicht wie Schlaf ist . Code nach der Schleife wird sofort ausgeführt, nur die Ausführung der Rückruffunktion wird verschoben.

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

Das erste Timeout wird auf gesetzt 3000 * 1, das zweite auf 3000 * 2und so weiter.


2
Es sei darauf hingewiesen, dass Sie startdiese Methode mit dieser Methode nicht zuverlässig in Ihrer Funktion verwenden können.
DBS

2
Schlechte Praxis - unnötige Speicherzuweisung.
Alexander Trakhimenok

Upvote für Kreativität, aber es ist verdammt schlechte Praxis. :)
Salivan

2
Warum ist es eine schlechte Praxis und warum gibt es Probleme mit der Speicherzuweisung? Hat diese Antwort die gleichen Probleme? stackoverflow.com/a/36018502/1337392
Flame_Phoenix

1
@Flame_Phoenix ist eine schlechte Praxis, da das Programm einen Timer für jede Schleife behält, wobei alle Timer gleichzeitig ausgeführt werden. Wenn also 1000 Iterationen vorhanden sind, werden zu Beginn 1000 Timer gleichzeitig ausgeführt.
Joakim

16

Das wird funktionieren

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

Versuchen Sie diese Geige: https://jsfiddle.net/wgdx8zqq/


1
Dies löst jedoch alle Timeout-Anrufe fast zur gleichen Zeit aus
Eddie

Das einzige, was ich sage, ich habe auf diese Weise geknackt, verwendet, $.Deferredaber es war ein anderes Szenario, um es funktionieren zu lassen, Daumen zu dir ..!
ArifMustafa

15

Ich denke du brauchst so etwas:

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

Testcode:

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

Hinweis: Die Verwendung von Warnungen blockiert die Ausführung von Javascript, bis Sie die Warnung schließen. Es ist möglicherweise mehr Code, als Sie angefordert haben, aber dies ist eine robuste wiederverwendbare Lösung.


15

Ich würde wahrscheinlich verwenden setInteval. So was,

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 ist viel besser als Settinterval. google es und du wirst es wissen
Airy

14
Ich google es ein wenig herum und habe nichts gefunden. Warum ist setInterval schlecht? Können Sie uns einen Link geben? oder ein Beispiel? Vielen Dank
Marcs

Ich denke, der Punkt war, dass SetInterval()selbst im Falle eines Fehlers oder Blocks immer wieder "Threads" entstehen.
Mateen Ulhaq

8

In ES6 (ECMAScript 2015) können Sie mit Generator und Intervall verzögert iterieren .

Generatoren, eine neue Funktion von ECMAScript 6, sind Funktionen, die angehalten und fortgesetzt werden können. Das Aufrufen von genFunc führt es nicht aus. Stattdessen wird ein sogenanntes Generatorobjekt zurückgegeben, mit dem wir die Ausführung von genFunc steuern können. genFunc () wird zunächst am Anfang seines Körpers angehalten. Die Methode genObj.next () setzt die Ausführung von genFunc bis zur nächsten Ausbeute fort. (ES6 erkunden)


Codebeispiel:

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

Wenn Sie also ES6 verwenden, ist dies der eleganteste Weg, um eine Schleife mit Verzögerung zu erreichen (meiner Meinung nach).


4

Sie können den RxJS- Intervalloperator verwenden . Das Intervall gibt alle x Sekunden eine Ganzzahl aus, und take gibt an, wie oft Zahlen ausgegeben werden müssen

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

Ich dachte nur, ich würde meine zwei Cent auch hier posten. Diese Funktion führt eine iterative Schleife mit einer Verzögerung aus. Siehe diese jsfiddle . Die Funktion ist wie folgt:

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

Zum Beispiel:

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

Wäre gleichbedeutend mit:

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

4

Ich mache das mit Bluebird's Promise.delayund Rekursion.

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 können Sie Folgendes tun:

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

In ES5 können Sie Folgendes tun:

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

Der Grund dafür ist, letdass Sie Variablen deklarieren können, die auf einen Bereich einer Blockanweisung oder einen Ausdruck beschränkt sind, für den sie verwendet werden, im Gegensatz zum varSchlüsselwort, das eine Variable global oder lokal für eine gesamte Funktion definiert, unabhängig vom Blockbereich.


1

Eine modifizierte Version von Daniel Vassallos Antwort mit Variablen, die in Parameter extrahiert wurden, um die Funktion wiederverwendbarer zu machen:

Definieren wir zunächst einige wesentliche Variablen:

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

Als nächstes sollten Sie die Funktion definieren, die Sie ausführen möchten. Dies wird i, der aktuelle Index der Schleife und die Länge der Schleife übergeben, falls Sie es brauchen:

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

Selbstausführende Version

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

Funktionsversion

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

schön und wie übergebe ich Daten an die Funktion ohne eine globale Variable
Sundara Prabu

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

1

Mach du es:

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>


Besser Konsolenprotokolle anstelle von Warnungen verwenden, war nicht sehr lustig, um die Warnungen für eine halbe Minute zu schließen;)
Hendry

Ja. Aha! Aber Anfrage ist wachsam ... huz
Nguyen Ba Danh - FAIC HN

Warum jQuery importieren?
Elias Soares

Entschuldigung ... es ist unnötig ... heh. Ich kenne keinen Post-Inhalt ... dies zuerst.
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
Ihre Funktionsnamen sind schrecklich, das ist der Hauptgrund, warum dieser Code so schwer zu lesen ist.
Mark Walters

0

So habe ich eine Endlosschleife mit einer Verzögerung erstellt, die unter bestimmten Bedingungen unterbrochen wird:

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

Der Schlüssel hier ist, ein neues Versprechen zu erstellen, das nach Zeitüberschreitung aufgelöst wird, und auf seine Lösung zu warten.

Natürlich brauchen Sie dafür asynchrone Unterstützung. Funktioniert in Knoten 8.


0

für den allgemeinen Gebrauch "vergiss normale Schleifen" und benutze diese Kombination von "setInterval" beinhaltet "setTimeOut" s: so (von meinen eigentlichen Aufgaben).

        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: Verstehe, dass das wirkliche Verhalten von (setTimeOut): Sie werden alle zur gleichen Zeit starten. "Die drei bla bla bla werden im selben Moment herunterzählen."

PS 2: Das Beispiel für eine Zeitschleife, aber für eine Reaktionsschleife können Sie Ereignisse verwenden, versprechen asynchrones Warten.


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
Bitte geben Sie stets zumindest kurze Beschreibung , um Ihren Code - Schnipsel, zumindest für andere sicher sein , dass Sie adressieren die Frage.
Hexfire

1
Code nur Antworten werden nicht empfohlen, da sie nicht viele Informationen für zukünftige Leser bieten. Bitte geben Sie eine Erklärung zu dem, was Sie geschrieben haben
WhatsThePoint

0

Meines Wissens wird die setTimeoutFunktion asynchron aufgerufen. Sie können die gesamte Schleife in eine asynchrone Funktion Promiseeinschließen und auf eine warten , die das setTimeout wie folgt enthält:

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

Und dann rufst du es so an:

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

Bitte nehmen Sie sich etwas Zeit, um ein gutes Verständnis der asynchronen Programmierung zu erlangen.


0

Probieren Sie es einfach aus

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

Ergebnis

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

-1

Hier ist eine Funktion, die ich zum Durchlaufen eines Arrays verwende:

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

Sie verwenden es so:

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

-1

Dieses Skript funktioniert für die meisten Dinge

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

Versuche dies...

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

Einfache Implementierung, bei der alle zwei Sekunden ein Textstück angezeigt wird, solange die Schleife ausgeführt wird.

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

-3

Versuche dies

//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);
  }
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.