Jak dodać opóźnienie w pętli JavaScript?


346

Chciałbym dodać opóźnienie / sen w whilepętli:

Próbowałem tak:

alert('hi');

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

Tylko pierwszy scenariusz jest prawdziwy: po pokazie alert('hi')będzie czekał 3 sekundy, następnie alert('hello')zostanie wyświetlony, ale następnie alert('hello')będzie się ciągle powtarzał.

Chciałbym tylko, aby po alert('hello')3 sekundach wyświetlał się po alert('hi')nim, musi on czekać 3 sekundy po raz drugi alert('hello')i tak dalej.

Odpowiedzi:


750

setTimeout()Funkcja nie jest blokowanie i natychmiast powrócić. Dlatego twoja pętla będzie iterować bardzo szybko i zainicjuje 3-sekundowe przekroczenie limitu czasu, jeden po drugim, w krótkich odstępach czasu. Dlatego pierwsze alerty pojawiają się po 3 sekundach, a cała reszta pojawia się bez przerwy.

Zamiast tego możesz użyć czegoś takiego:

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

Możesz także go uporządkować, używając funkcji samowywołania, przekazując liczbę iteracji jako argument:

(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
Czy użycie rekurencji w celu zaimplementowania tego nie byłoby ostatecznie przepełnione stosu? Jeśli chcesz zrobić milion iteracji, jaki byłby lepszy sposób na wdrożenie tego? Może ustaw Interwał, a następnie go wyczyść, jak rozwiązanie Abla poniżej?
Adam

7
@Adam: rozumiem, że skoro setTimeout nie blokuje, to nie jest wycofanie - okno stosu zamyka się po każdym setTimeout i tylko jeden setTimeout czeka na wykonanie ... Prawda?
Joe

3
Jak by to działało podczas iteracji obiektu takiego jak for inpętla?
vsync

1
@vsync Zajrzyj doObject.keys()
Braden Best

1
@joey Mylisz się setTimeoutz setInterval. Limity czasu są domyślnie niszczone, gdy wywoływane jest wywołanie zwrotne.
cdhowie

72

Wypróbuj coś takiego:

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

69

Jeśli używasz ES6, możesz to letosiągnąć:

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

Co letrobi to stwierdzenie idla każdej iteracji , a nie pętli. W ten sposób przekazujemy setTimeoutdokładnie to, czego chcemy.


1
Podziękować! Nie pomyślałbym o tej metodzie sam. Rzeczywisty zakres bloków. Wyobraź sobie, że ...
Sophia Gold,

1
Uważam, że ma to te same problemy z alokacją pamięci, co odpowiedź opisana w stackoverflow.com/a/3583795/1337392
Flame_Phoenix

1
@Flame_Phoenix Jakie problemy z alokacją pamięci?
4castle,

1
Wywołanie setTimeout synchronicznie oblicza wartość i*3000argumentu wewnątrz pętli i przekazuje ją setTimeoutwedług wartości. Użycie letjest opcjonalne i niezwiązane z pytaniem i odpowiedzią.
traktor53

@Flame_Phoenix wspomniał, że w tym kodzie występują problemy. Zasadniczo przy pierwszym przejściu tworzysz licznik, a następnie natychmiast powtarzasz pętlę, aż do końca pętli według warunku ( i < 10), dzięki czemu będziesz mieć równolegle wiele liczników pracujących równolegle, które tworzą alokację pamięci, a gorzej na większej liczbie iteracji.
XCanG 17.07.19

63

Ponieważ ES7 jest lepszym sposobem na oczekiwanie na pętlę:

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

Gdy silnik dotrze do awaitczęści, ustawia limit czasu i zatrzymuje wykonywanieasync function . Następnie po upływie limitu czasu wykonywanie jest kontynuowane w tym punkcie. Jest to bardzo przydatne, ponieważ można opóźnić (1) zagnieżdżone pętle, (2) warunkowo, (3) zagnieżdżone funkcje:

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

Odniesienie do MDN

Chociaż ES7 jest teraz obsługiwany przez NodeJS i nowoczesne przeglądarki, możesz go przetransponować za pomocą BabelJS, aby działał wszędzie.


Działa dla mnie dobrze. Chcę tylko zapytać, czy jeśli chcę przerwać pętlę, jak mogę to zrobić, gdy używasz czekania?
Sachin Shah,

@sachin break;może?
Jonas Wilms,

Dzięki za to rozwiązanie. Miło jest korzystać ze wszystkich istniejących struktur kontrolnych i nie trzeba wymyślać kontynuacji.
Gus

To wciąż tworzyłoby różne liczniki i rozwiązywałyby się w różnych momentach, a nie w sekwencji?
David Yell

@JonasWilms Wygląda na to, że całkowicie mi brakowało przycisku „Run snippet”: facepalm:
David Yell

24

Innym sposobem jest zwielokrotnienie czasu oczekiwania, ale należy pamiętać, że to nie jest jak sen . Kod po pętli zostanie wykonany natychmiast, tylko wykonanie funkcji zwrotnej jest odroczone.

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

Pierwszy limit czasu zostanie ustawiony na 3000 * 1, drugi na 3000 * 2itd.


2
Warto zauważyć, że nie można w sposób niezawodny korzystać startz funkcji za pomocą tej metody.
DBS,

2
Zła praktyka - niepotrzebny przydział pamięci.
Alexander Trakhimenok

Głosuj za kreatywnością, ale to cholernie zła praktyka. :)
Salivan

2
Dlaczego jest to zła praktyka i dlaczego występują problemy z alokacją pamięci? Czy ta odpowiedź ma takie same problemy? stackoverflow.com/a/36018502/1337392
Flame_Phoenix

1
@Flame_Phoenix to zła praktyka, ponieważ program zachowa jeden licznik czasu dla każdej pętli, przy czym wszystkie liczniki będą działały w tym samym czasie. Jeśli więc istnieje 1000 iteracji, na początku będzie uruchomionych 1000 timerów.
Joakim,

16

To zadziała

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

Wypróbuj to skrzypce: https://jsfiddle.net/wgdx8zqq/


1
Spowoduje to jednak wywołanie wszystkich przekroczeń limitu czasu w tym samym czasie
Eddie

jedyne, co mówię, złamałem w ten sposób, użyłem, $.Deferredale był inny scenariusz, żeby to zadziałało, kciuki do ciebie ..!
ArifMustafa,

15

Myślę, że potrzebujesz czegoś takiego:

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

Kod testowy:

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

Uwaga: używanie alertów opóźnia wykonywanie javascript, dopóki alarm nie zostanie zamknięty. Może to być więcej kodu niż prosiłeś, ale jest to solidne rozwiązanie wielokrotnego użytku.


15

Prawdopodobnie skorzystałbym setInteval. Lubię to,

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 jest znacznie lepszy niż settinterval. google to i będziesz wiedzieć
Airy

14
Przeszukuję go trochę i nic nie znalazłem, dlaczego setInterval jest zły? Czy możesz podać nam link? czy przykład? Dzięki
Marcs

Chyba punktem było to, że SetInterval()utrzymuje tarła „wątki”, nawet w razie jakiegoś błędu lub bloku.
Mateen Ulhaq

8

W ES6 (ECMAScript 2015) możesz iterować z opóźnieniem z generatorem i interwałem.

Generatory, nowa funkcja ECMAScript 6, to funkcje, które można wstrzymać i wznowić. Wywołanie genFunc go nie wykonuje. Zamiast tego zwraca tak zwany obiekt generatora, który pozwala nam kontrolować wykonywanie genFunc. genFunc () jest początkowo zawieszony na początku swojego ciała. Metoda genObj.next () kontynuuje wykonywanie genFunc, aż do następnej wydajności. (Odkrywanie ES6)


Przykład kodu:

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

Więc jeśli używasz ES6, to najbardziej elegancki sposób na uzyskanie pętli z opóźnieniem (moim zdaniem).


4

Możesz użyć operatora interwału RxJS . Interwał emituje liczbę całkowitą co x liczbę sekund, a take określa, ile razy ma on emitować liczby

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

Pomyślałem, że też tutaj zamieściłem moje dwa centy. Ta funkcja uruchamia pętlę iteracyjną z opóźnieniem. Zobacz to jsfiddle . Funkcja jest następująca:

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

Na przykład:

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

Byłby równoważny z:

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

4

Robię to za pomocą Bluebirda Promise.delayi rekurencji.

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

W ES6 możesz wykonać następujące czynności:

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

W ES5 możesz zrobić jako:

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

Powodem jest to, letże pozwala deklarować zmienne, które są ograniczone do zakresu instrukcji blokowej lub wyrażenia, w którym jest używana, w przeciwieństwie do varsłowa kluczowego, które definiuje zmienną globalnie lub lokalnie do całej funkcji, niezależnie od zakresu bloku.


1

Zmodyfikowana wersja odpowiedzi Daniela Vassallo, ze zmiennymi wyodrębnionymi do parametrów, aby uczynić funkcję bardziej użyteczną:

Najpierw zdefiniujmy niektóre podstawowe zmienne:

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

Następnie powinieneś zdefiniować funkcję, którą chcesz uruchomić. Zostanie przekazany i, bieżący indeks pętli i długość pętli, w razie potrzeby:

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

Wersja samodzielna

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

Wersja funkcjonalna

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

fajny i jak przekazać dane do funkcji bez zmiennej globalnej
Sundara Prabu

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

1

Ty to zrób:

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>


lepiej korzystać z dzienników konsoli zamiast alertów, zamknięcie alertów na pół minuty nie było zabawne;)
Hendry

Tak. Widzę! Ale prośba jest czujna ... huz
Nguyen Ba Danh - FAIC HN

Dlaczego warto importować jQuery?
Elias Soares,

Przepraszam ... to niepotrzebne .. heh. Nie znam treści postów ... ten pierwszy.
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
Nazwy funkcji są przerażające, to główny powód, dla którego ten kod jest tak trudny do odczytania.
Mark Walters

0

Oto jak stworzyłem nieskończoną pętlę z opóźnieniem, które psuje się pod pewnym warunkiem:

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

Kluczem tutaj jest stworzenie nowej obietnicy, która zostanie rozwiązana po upływie limitu czasu, i oczekiwanie na jej rozwiązanie.

Oczywiście potrzebujesz asynchronizacji / czekaj na wsparcie. Działa w węźle 8.


0

do powszechnego użytku „zapomnij o normalnych pętlach” i użyj tej kombinacji „setInterval” obejmuje „setTimeOut”: jak to (z moich prawdziwych zadań).

        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: Zrozum, że prawdziwe zachowanie (setTimeOut): wszystkie zaczną się w tym samym czasie „trzy bla bla bla zaczną odliczać w tym samym momencie”, więc ustal inny czas na zorganizowanie wykonania.

PS 2: przykład dla pętli czasowej, ale dla pętli reakcyjnych możesz użyć zdarzeń, obiecaj, że async czeka.


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
Zawsze dołącz przynajmniej krótki opis fragmentów kodu, przynajmniej dla innych, aby upewnić się, że odpowiesz na pytanie.
Hexfire

1
Odpowiedzi tylko na kod nie są zachęcane, ponieważ nie zawierają zbyt wielu informacji dla przyszłych czytelników, prosimy o wyjaśnienie tego, co napisaliście
WhatsThePoint

0

Według mojej wiedzy setTimeoutfunkcja ta jest wywoływana asynchronicznie. Możesz zawinąć całą pętlę w funkcję asynchroniczną i poczekać na Promisezawartość setTimeout, jak pokazano:

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

A potem wywołujesz tak:

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

Poświęć trochę czasu, aby dobrze zrozumieć programowanie asynchroniczne.


0

Po prostu spróbuj tego

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

Wynik

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

-1

Oto funkcja, której używam do zapętlania tablicy:

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

Używasz go w ten sposób:

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

-1

Ten skrypt działa na większość rzeczy

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

Spróbuj tego...

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

Prosta implementacja pokazywania fragmentu tekstu co dwie sekundy, dopóki pętla jest uruchomiona.

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

-3

Spróbuj tego

//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);
  }
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.