簡體   English   中英

如何在 JavaScript 循環中添加延遲?

[英]How do I add a delay in a JavaScript loop?

我想在while循環中添加延遲/睡眠:

我這樣試過:

alert('hi');

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

只有第一種情況是正確的:在顯示alert('hi')之后,它將等待 3 秒,然后將顯示 alert('hello' alert('hello')但隨后alert('hello')將不斷重復。

我想要的是,在 alert alert('hi')之后 3 秒顯示alert('hello')之后,第二次alert('hello')需要等待 3 秒,依此類推。

setTimeout()函數是非阻塞的,將立即返回。 因此,您的循環將非常快速地迭代,並且它將快速連續地啟動 3 秒超時觸發器。 這就是為什么您的第一個警報會在 3 秒后彈出,然后所有其他警報都會連續出現,沒有任何延遲。

您可能想改用這樣的東西:

 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

您還可以通過使用自調用函數來整理它,將迭代次數作為參數傳遞:

 (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

由於 ES7 有更好的方法來等待循環:

// Returns a Promise that resolves after "ms" Milliseconds
const timer = ms => 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();

當引擎到達await部分時,它會設置一個超時並停止執行async function 然后當超時完成時,執行將在該點繼續。 這非常有用,因為您可以延遲 (1) 嵌套循環,(2) 有條件地,(3) 嵌套函數:

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

MDN 上的參考資料

雖然 NodeJS 和現代瀏覽器現在支持 ES7,但您可能希望使用BabelJS 對其進行轉譯,以便它可以在任何地方運行。

如果使用 ES6,您可以使用 for 循環來實現:

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

它為每次迭代聲明i ,這意味着超時是 + 1000 之前的值。這樣,傳遞給setTimeout的正是我們想要的。

嘗試這樣的事情:

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

另一種方法是乘以超時時間,但請注意,這不像 sleep 循環后的代碼會立即執行,只有回調函數的執行被延遲。

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

第一個超時將設置為3000 * 1 ,第二個超時將設置為 3000 3000 * 2 ,依此類推。

這將起作用

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

試試這個小提琴: https ://jsfiddle.net/wgdx8zqq/

我認為你需要這樣的東西:

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

測試代碼:

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

注意:使用警報會停止 javascript 的執行,直到您關閉警報。 它可能比您要求的代碼更多,但這是一個強大的可重用解決方案。

我可能會使用setInterval ,如下所示:

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

您可以創建一個承諾setTimeoutsleep函數。 這使您可以使用async / await編寫代碼而無需回調和熟悉for循環控制流程。

 const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); (async () => { for (let i = 0; i < 10; i++) { console.log(i); await sleep(1000); } console.log("done"); })();

在 Node 中,您可以使用timers/promises來避免 promisification 步驟(如果您的舊 Node 版本不支持該功能,上述代碼也可以正常工作):

const {setTimeout: sleep} = require("timers/promises");

// same code as above

無論如何,由於 JS 是單線程的,所以超時是異步的是一件好事。 否則,瀏覽器將沒有機會重新繪制 UI,從而導致用戶界面凍結。

在我看來,在循環中添加延遲的更簡單、最優雅的方法是這樣的:

names = ['John', 'Ana', 'Mary'];

names.forEach((name, i) => {
 setTimeout(() => {
  console.log(name);
 }, i * 1000);  // one sec interval
});

在 ES6 (ECMAScript 2015) 中,您可以使用生成器和間隔進行延遲迭代。

生成器是 ECMAScript 6 的新特性,是可以暫停和恢復的函數。 調用 genFunc 不會執行它。 相反,它返回一個所謂的生成器對象,讓我們控制 genFunc 的執行。 genFunc() 最初在其主體的開頭暫停。 genObj.next() 方法繼續執行 genFunc,直到下一個 yield。 (探索 ES6)


代碼示例:

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

因此,如果您使用的是 ES6,那是實現延遲循環的最優雅方式(我認為)。

我使用 Bluebird 的Promise.delay和遞歸來做到這一點。

 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>

在 ES6 中,您可以執行以下操作:

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

在 ES5 中,你可以這樣做:

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

原因是, let允許您聲明僅限於塊語句范圍或使用它的表達式的變量,這與var關鍵字不同,它在全局范圍內定義變量,或者在本地定義整個函數而不考慮塊范圍.

只是想我也會在這里張貼我的兩分錢。 這個函數運行一個有延遲的迭代循環。 看到這個 jsfiddle 功能如下:

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

例如:

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

相當於:

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

您可以使用 RxJSinterval運算符 intervalx秒發出一個整數,並且take指定它發出這些數字的次數。

 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>

據我所知, setTimeout函數是異步調用的。 您可以做的是將整個循環包裝在異步函數中並等待包含 setTimeout 的Promise ,如下所示:

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

然后你像這樣調用它:

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

請花一些時間來很好地理解異步編程。

除了 10 年前公認的答案之外,使用更現代的 Javascript 可以使用async / await / Promise()或生成器函數來實現正確的行為。 (其他答案中建議的不正確行為將設置一系列 3 秒警報,而不管“接受” alert() - 或完成手頭的任務)

使用async / await / Promise()

 alert('hi'); (async () => { for(let start = 1; start < 10; start++) { await new Promise(resolve => setTimeout(() => { alert('hello'); resolve(); }, 3000)); } })();

使用生成器函數:

 alert('hi'); let func; (func = (function*() { for(let start = 1; start < 10; start++) { yield setTimeout(() => { alert('hello'); func.next(); }, 3000); } })()).next();

無功能的解決方案

我參加聚會有點晚了,但是有一個不使用任何功能的解決方案:

alert('hi');

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

非常簡單的單行解決方案,具有實際的異步等待延遲(沒有排隊的 setTimeout):

以下(自執行匿名)函數在循環之間創建一個實際延遲,而不是多個具有不同超時的setTimeout ,這可能會弄亂內存。

  • 在 100 個循環中的每一個循環中,它都會await一個new Promiseresolve
  • 只有在setTimeout在 90 毫秒后“允許”它之后才會發生這種情況。 在此之前,代碼會被 async-await / pending Promise阻塞。

 (async () => { for (let i=0; i<100; i++) { await new Promise((resolve) => {setTimeout(() => {document.write(`${i} `); resolve(true)}, 90)}); } })()

 var startIndex = 0; var data = [1, 2, 3]; var timeout = 1000; function functionToRun(i, length) { alert(data[i]); } (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);

Daniel Vassallo 答案的修改版本,將變量提取到參數中以使函數更可重用:

首先讓我們定義一些基本變量:

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

接下來,您應該定義要運行的函數。 如果需要,這將傳遞 i、循環的當前索引和循環的長度:

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

自執行版本

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

功能版

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

試試這個

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

結果

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

以下是我如何創建一個無限循環,其延遲在特定條件下中斷:

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

這里的關鍵是創建一個新的通過超時解決的 Promise,並等待它的解決。

顯然你需要 async/await 支持。 在節點 8 中工作。

對於常用的“忘記正常循環”並使用“setInterval”的這種組合包括“setTimeOut”:像這樣(來自我的實際任務)。

        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:了解(setTimeOut)的真實行為:它們都將在同一時間開始“三個bla bla bla將在同一時刻開始倒計時”,因此請設置不同的超時時間來安排執行。

PS 2:計時循環的示例,但是對於反應循環,您可以使用事件,promise async await ..

   let counter =1;
   for(let item in items) {
        counter++;
        setTimeout(()=>{
          //your code
        },counter*5000); //5Sec delay between each iteration
    }
const autoPlayer = (arr = [1, 2, 3, 4, 5]) => {
  // Base case:
  if (arr.length < 1) return

  // Remove the first element from the array.
  const item = arr.shift()

  // Set timout 
  setTimeout(() => {
    console.log('Hello, world!', item)  // Visualisation.
    autoPlayer() // Call function again.
  }, 1000) // Iterate every second.
}

嘿,我知道這篇文章很老了,但是這段代碼“循環”並使用遞歸方法為其添加了延遲。 我認為您不能“實際上”根據閱讀其他人的各種評論來延遲循環本身的迭代。 也許這可以幫助某人! 基本上該函數接受一個數組(在本例中)。 在每次迭代中,都會調用setTimeout Javascript 方法。 setTimeout函數的計時器到期時,該函數會無限期地再次調用自身,但在每次調用時,數組都會變小,直到達到基本情況。 我希望這可以幫助其他人。

/* 
  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
*/

這是我用於循環數組的函數:

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

你像這樣使用它:

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

這個腳本適用於大多數事情

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

 <!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>

 var timer, i = 10; function myLoop () { // create a loop function timer = setTimeout(function () { document.getElementById("demo").innerHTML = i; i--; if (i >= 0) { myLoop(); } else { clearTimeout(timer); // clear timeout document.getElementById("demo").innerHTML = "DOARRRR .."; } }, 1000); } myLoop(); 
 <p id="demo">count</p> 

嘗試這個...

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

你來弄吧:

 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>

var count = 0;

//Parameters:
//  array: []
//  fnc: function (the business logic in form of function-,what you want to execute)
//  delay: milisecond  

function delayLoop(array,fnc,delay){
    if(!array || array.legth == 0)return false;
    setTimeout(function(data){ 
        var data = array[count++];
        fnc && fnc(data);
        //recursion...
        if(count < array.length)
            delayLoop(array,fnc,delay);
        else count = 0;     
    },delay);
}

只要循環運行,每兩秒顯示一段文本的簡單實現。

for (var i = 0; i < foo.length; i++) {
   setInterval(function(){ 
     console.log("I will appear every 2 seconds"); 
   }, 2000);
  break;
};
var loop=(i,time)=>new Promise(r=>{
 alert("aaa",i);
 if(i>0)
 setTimeout(()=>{
    r([i-1,time]);
 },time);
}).then(v=>loop(v[0],v[1]))

loop(3,1000);

試試看

嘗試這個

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM