Javascript,setTimeout循环?


91

因此,我正在开发一个音乐程序,该程序需要多个javascript元素才能彼此同步。我一直在使用setInterval,它起初确实很好用,但是随着时间的流逝,元素逐渐变得不同步,这对音乐程序来说是不好的。

我已经在线阅读了setTimeout更准确的信息,并且可以以某种方式设置setTimeout循环,但是我还没有找到一个通用的版本来说明这是如何实现的。有人可以告诉我一个使用setTimeout无限循环的基本示例。

谢谢。另外,如果有一种方法可以通过setInterval甚至其他函数来实现更多同步结果,请告诉我。

编辑:

基本上我有一些像这样的功能:

//drums
setInterval(function {
//code for the drums playing goes here
},8000);

//chords
setInterval(function {
//code for the chords playing goes here
},1000);

//bass
setInterval(function {
//code for the bass playing goes here
},500);

最初它工作得非常好,但是在大约一分钟的过程中,由于我读过setInterval时声音变得明显不同步,我读过setTimeout可以更加一致地准确。


2
您为什么不发布一些代码,向我们展示您想要实现的目标,我们可以为您提供更好的答案。
2014年

1
我已经在线阅读了setTimeout更准确的信息:您在哪里读的?包括一个链接。我假设您可能会遇到这种情况,setTimeout您可以计算出延迟实际上是调整下一次超时的时间。
马特·伯兰

2
requestAnimationFrame呢 您只需参考每次requestAnimationFrame回调运行时音频的时间。
贾斯珀(Jasper)

5
两种类型的计时器都不能真正保证精确。给定的毫秒数只是最短的等待时间,但是以后仍可以调用该函数。如果您要协调多个时间间隔,请尝试合并为一个,控制时间间隔。
乔纳森·洛诺夫斯基

1
如果您确实想将音乐同步到屏幕上的某些内容,则在更新DOM时需要参考音频的时间进度。否则,大多数情况下事情会变得不同步。
贾斯珀

Answers:


181

您可以setTimeout使用递归创建一个循环:

function timeout() {
    setTimeout(function () {
        // Do Something Here
        // Then recall the parent function to
        // create a recursive loop.
        timeout();
    }, 1000);
}

这个问题setInterval()setTimeout()是没有保证你的代码将在指定时间运行。通过setTimeout()递归使用和调用它,可以确保超时之前的所有先前操作在代码的下一次迭代开始之前已完成。


1
这种方法和使用方法有什么区别setInterval
贾斯珀

4
这种方法的问题在于,如果该功能花费的时间超过1000毫秒,那么一切都会变得毫无意义。这不能保证每1000ms执行一次。setInterval是。
TJC 2014年

@TJC:这在很大程度上取决于您要实现的目标。上一个函数在下一次迭代之前完成可能更重要,也许没有。
马特·伯兰

4
@TJC正确,但是如果您的先前操作在setInterval()重新执行之前未完成,则您的变量和/或数据可能会很快变得不同步。例如,如果我要处理数据,并且服务器花费的时间超过1秒,setInterval()那么在继续执行下一个操作之前,使用我以前的数据将无法完成处理。使用这种方法,不能保证您的功能会每秒启动。但是,可以保证在下一个间隔开始之前,您先前的数据将完成处理。
War10ck

1
@ War10ck,在基于音乐的环境中,我假设它不会被用于在顺序重要的地方设置变量或ajax调用。
TJC 2014年

30

仅作为补充。如果您需要传递变量并对其进行迭代,则可以这样做:

function start(counter){
  if(counter < 10){
    setTimeout(function(){
      counter++;
      console.log(counter);
      start(counter);
    }, 1000);
  }
}
start(0);

输出:

1
2
3
...
9
10

每秒一线。


12

鉴于时间都不是很准确,使用setTimeout一种更精确的方法是计算自上次迭代以来的延迟时间,然后根据需要调整下一次迭代。例如:

var myDelay = 1000;
var thisDelay = 1000;
var start = Date.now();

function startTimer() {    
    setTimeout(function() {
        // your code here...
        // calculate the actual number of ms since last time
        var actual = Date.now() - start;
        // subtract any extra ms from the delay for the next cycle
        thisDelay = myDelay - (actual - myDelay);
        start = Date.now();
        // start the timer again
        startTimer();
    }, thisDelay);
}

因此,它第一次等待(至少)1000毫秒,当您的代码被执行时,可能会稍晚一些,例如1046毫秒,因此我们将从下一个周期的延迟中减去46毫秒,下一个延迟将是只有954毫秒。这不会阻止计时器延迟触发(这是可以预期的),但可以帮助您阻止延迟积聚。(请注意:您可能要检查thisDelay < 0导致延迟超过目标延迟两倍的数字,并且错过了一个周期-由您决定如何处理这种情况)。

当然,这可能无法帮助您保持多个计时器同步,在这种情况下,您可能想弄清楚如何使用同一计时器控制所有计时器。

因此,查看您的代码,所有延迟都是500的倍数,因此您可以执行以下操作:

var myDelay = 500;
var thisDelay = 500;
var start = Date.now();
var beatCount = 0;

function startTimer() {    
    setTimeout(function() {
        beatCount++;
        // your code here...
        //code for the bass playing goes here  

        if (count%2 === 0) {
            //code for the chords playing goes here (every 1000 ms)
        }

        if (count%16) {
            //code for the drums playing goes here (every 8000 ms)
        }

        // calculate the actual number of ms since last time
        var actual = Date.now() - start;
        // subtract any extra ms from the delay for the next cycle
        thisDelay = myDelay - (actual - myDelay);
        start = Date.now();
        // start the timer again
        startTimer();
    }, thisDelay);
}

1
+1整洁的方法。我从未想过要抵消下一次运行的超时时间。考虑到JavaScript不是多线程的,并且不能保证以一致的时间间隔触发,这可能会使您接近精确的度量。
War10ck

这很棒!我将尝试一下,让您知道它的运行情况。我的代码很庞大,因此可能需要一段时间
-user3084366

考虑:让此代码在固定的(即使已如上所述纠正)时间循环上触发可能实际上是思考问题的错误方法。可能是您实际上应该将每个间隔长度设置为“下一次应播放的时间减去当前时间”。
mwardm

您提供了我刚刚写下的解决方案的更好版本。我喜欢您的变量名使用音乐中的语言,并且您尝试管理累积的延迟,而我什至没有尝试过。
米格尔·瓦伦西亚

8

处理音频定时的最佳方法是使用Web Audio Api,它具有独立的时钟,无论主线程中发生了什么,该时钟都是准确的。克里斯·威尔逊(Chris Wilson)在这里提供了很好的解释,示例等:

http://www.html5rocks.com/zh-CN/tutorials/audio/scheduling/

环顾这个网站,了解更多Web Audio API,它的开发目的完全是为了您要做什么。


我真希望更多的人对此表示支持。答案的使用setTimeout范围从不足到极其复杂。使用本机函数似乎是一个更好的主意。如果API不能满足您的目的,那么我建议您尝试寻找可靠的第三方调度库。
threeve


3

根据您的要求

只是给我看一个使用setTimeout循环的基本例子

我们有以下示例可以为您提供帮助

var itr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var  interval = 1000; //one second
itr.forEach((itr, index) => {

  setTimeout(() => {
    console.log(itr)
  }, index * interval)
})


1

我在工作中使用这种方式:在这种情况下,“忘记通用循环”并使用“ 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:时序循环的示例,但对于反应循环,您可以使用事件,并保证异步等待。


1

setTimeout循环问题与解决方案

// it will print 5 times 5.
for(var i=0;i<5;i++){
setTimeout(()=> 
console.log(i), 
2000)
}               // 5 5 5 5 5

// improved using let
for(let i=0;i<5;i++){
setTimeout(()=> 
console.log('improved using let: '+i), 
2000)
}

// improved using closure
for(var i=0;i<5;i++){
((x)=>{
setTimeout(()=> 
console.log('improved using closure: '+x), 
2000)
})(i);
} 


1
知道为什么它在var和let之间表现不同吗?
杰伊,

1
@Jay ...它们之间的区别在于var是函数范围的,而let是块范围的。更多的澄清你可以通过medium.com/@josephcardillo/...
瓦希德阿赫塔尔

0

function appendTaskOnStack(task, ms, loop) {
    window.nextTaskAfter = (window.nextTaskAfter || 0) + ms;

    if (!loop) {
        setTimeout(function() {
            appendTaskOnStack(task, ms, true);
        }, window.nextTaskAfter);
    } 
    else {
        if (task) 
            task.apply(Array(arguments).slice(3,));
        window.nextTaskAfter = 0;
    }
}

for (var n=0; n < 10; n++) {
    appendTaskOnStack(function(){
        console.log(n)
    }, 100);
}


1
对您的解决方案的解释将不胜感激!

0

正如其他人指出的那样,Web Audio API具有更好的计时器。

但是总的来说,如果这些事件持续发生,那么如何将它们全部置于同一计时器上呢?我在考虑步进音序器的工作方式。

实际上,看起来像这样吗?

var timer = 0;
var limit = 8000; // 8000 will be the point at which the loop repeats

var drumInterval = 8000;
var chordInterval = 1000;
var bassInterval = 500;

setInterval(function {
    timer += 500;

    if (timer == drumInterval) {
        // Do drum stuff
    }

    if (timer == chordInterval) {
        // Do chord stuff
    }

    if (timer == bassInterval) {
        // Do bass stuff
    }

    // Reset timer once it reaches limit
    if (timer == limit) {
        timer = 0;
    }

}, 500); // Set the timer to the smallest common denominator

-2

我认为最好在函数结尾处超时。

function main(){
    var something; 
    make=function(walkNr){
         if(walkNr===0){
           // var something for this step      
           // do something
         }
         else if(walkNr===1){
           // var something for that step 
           // do something different
         }

         // ***
         // finally
         else if(walkNr===10){
           return something;
         }
         // show progress if you like
         setTimeout(funkion(){make(walkNr)},15,walkNr++);  
   }
return make(0);
}   

这三个函数是必需的,因为第二个函数中的var每次都将被默认值覆盖。当程序指针到达setTimeout时,已经计算出一个步骤。然后,仅屏幕需要一点时间。


-2

在代码中使用let代替var:

for(let i=1;i<=5;i++){setTimeout(()=>{console.log(i)},1000);}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.