如何使 `setInterval` 的行为更加同步,或者如何改用 `setTimeout`?

IT技术 javascript settimeout setinterval timing
2021-02-07 13:52:57

我正在开发一个需要多个 JavaScript 元素与另一个元素同步的音乐程序。我一直在使用setInterval,它最初效果很好。然而,随着时间的推移,元素逐渐变得不同步,这在音乐节目中是不利的。

我在网上读过setTimeout更准确的,你可以setTimeout以某种方式循环。但是,我还没有找到一个通用版本来说明这是如何实现的。

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

//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可以更一致地准确。

有人可以向我展示一个使用setTimeout无限循环的基本示例吗?或者,如果有一种方法可以使用setInterval甚至其他函数实现更多同步结果,请告诉我。

6个回答

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

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

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

@War10ck,鉴于基于音乐的环境,我认为它不会用于在顺序重要的情况下设置变量或 ajax 调用。
2021-03-17 13:52:57
@TJC:这在很大程度上取决于您想要实现的目标。在下一次迭代之前完成前一个函数可能更重要,也可能不完成。
2021-03-21 13:52:57
这种方法和使用有setInterval什么区别
2021-03-23 13:52:57
@TJC 正确,但是如果您之前的操作在setInterval()重新执行之前没有完成,您的变量和/或数据可能会很快失去同步。例如,如果我正在对数据进行 ajaxing,并且服务器的响应时间超过 1 秒,setInterval()那么在我的下一个操作继续之前,使用我以前的数据不会完成处理。使用这种方法,不能保证您的函数每秒都会启动。但是,可以保证您之前的数据将在下一个间隔开始之前完成处理。
2021-03-24 13:52:57
这种方法的问题在于,如果函数的完成时间超过 1000 毫秒,它就会全部失败。这不能保证每 1000 毫秒执行一次。setInterval 是。
2021-04-07 13:52:57

只能补充。如果您需要传递一个变量并对其进行迭代,您可以这样做:

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

输出:

1
2
3
...
9
10

每秒一行。

鉴于这两种时间都不会非常准确,一种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);
}
这很棒!我要试试这个,让你知道它是怎么回事。我的代码很大,所以可能需要一段时间
2021-03-22 13:52:57
考虑:在固定的(即使已纠正,如上所述)时间循环上触发此代码实际上可能是考虑问题的错误方式。可能是您实际上应该将每个间隔长度设置为“下次播放时间减去当前时间”。
2021-03-30 13:52:57
您制作了我刚刚写下的解决方案的更好版本。我喜欢你的变量名使用来自音乐的语言,并且你尝试管理累积的延迟,我什至没有尝试过。
2021-04-09 13:52:57
+1 整洁的方法。我从来没有想过抵消下一次运行的超时时间。考虑到 JavaScript 不是多线程的,并且不能保证在一致的时间间隔内触发,这可能会让您尽可能接近精确的测量值。
2021-04-11 13:52:57

处理音频时序的最佳方法是使用 Web Audio Api,它有一个独立的时钟,无论主线程中发生什么,它都是准确的。Chris Wilson 在这里提供了很好的解释、示例等:

http://www.html5rocks.com/en/tutorials/audio/scheduling/

浏览此站点以获取更多 Web Audio API,它的开发正是为了满足您的需求。

我真的希望更多的人会赞成这一点。答案使用setTimeout范围从不足到极其复杂。使用本机函数似乎是一个更好的主意。如果 API 不能满足您的目的,那么我建议您尝试寻找可靠的第三方调度库。
2021-04-01 13:52:57

根据您的要求

给我看一个使用 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)
})