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.

olidev
źródło

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

Daniel Vassallo
źródło
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();
Cji
źródło
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.

Saket Mehta
źródło
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.

Jonas Wilms
źródło
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.

Felix Kling
źródło
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/

Gsvp Nagaraju
źródło
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.

BGerrissen
źródło
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);
Abel Terefe
źródło
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).

Itay Radotzki
źródło
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>

Vlad Bezden
źródło
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);
}
D Slee
źródło
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>

Dave Bryand
źródło
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.

Tabish
źródło
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
Jasdeep Khalsa
źródło
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
    }
Ali Azhar
źródło
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>

Nguyen Ba Danh - FAIC HN
źródło
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
*/
użytkownik2913925
źródło
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.


źródło
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.

Mohamed Abulnasr
źródło
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.
źródło
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.

Questionare232
źródło
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
Shirantha Madusanka
źródło
-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
}
PJeremyMalouf
źródło
-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);
}
Jaketr00
źródło
-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);
}
BUT PHILL
źródło
-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;
};
squeekyDave
źródło
-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);
  }
}
Steve Jiang
źródło