如何使“ setInterval”的行为更同步,或者如何使用“ setTimeout”代替?

我正在开发一个音乐程序,它需要多个 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或甚至其他功能,请让我知道。

247650 次浏览

You can create a setTimeout loop using recursion:

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

The problem with setInterval() and setTimeout() is that there is no guarantee your code will run in the specified time. By using setTimeout() and calling it recursively, you're ensuring that all previous operations inside the timeout are complete before the next iteration of the code begins.

Given that neither time is going to be very accurate, one way to use setTimeout to be a little more accurate is to calculate how long the delay was since the last iteration, and then adjust the next iteration as appropriate. For example:

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

So the first time it'll wait (at least) 1000 ms, when your code gets executed, it might be a little late, say 1046 ms, so we subtract 46 ms from our delay for the next cycle and the next delay will be only 954 ms. This won't stop the timer from firing late (that's to be expected), but helps you to stop the delays from pilling up. (Note: you might want to check for thisDelay < 0 which means the delay was more than double your target delay and you missed a cycle - up to you how you want to handle that case).

Of course, this probably won't help you keep several timers in sync, in which case you might want to figure out how to control them all with the same timer.

So looking at your code, all your delays are a multiple of 500, so you could do something like this:

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

I think it's better to timeout at the end of the function.

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

This three functions are necessary because vars in the second function will be overwritten with default value each time. When the program pointer reach the setTimeout one step is already calculated. Then just the screen needs a little time.

The best way to deal with audio timing is with the Web Audio Api, it has a separate clock that is accurate regardless of what is happening in the main thread. There is a great explanation, examples, etc from Chris Wilson here:

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

Have a look around this site for more Web Audio API, it was developed to do exactly what you are after.

Only to supplement. If you need to pass a variable and iterate it, you can do just like so:

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

Output:

1
2
3
...
9
10

One line per second.

I use this way in work life: "Forget common loops" in this case and use this combination of "setInterval" includes "setTimeOut"s:

    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: Understand that the real behavior of (setTimeOut): they all will start in same time "the three bla bla bla will start counting down in the same moment" so make a different timeout to arrange the execution.

PS 2: the example for timing loop, but for a reaction loops you can use events, promise async await ..

Use let instead of var in code :

for(let i=1;i<=5;i++){setTimeout(()=>{console.log(i)},1000);}

setTimeout loop problem with solution

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

Use setInterval()

setInterval(function(){
alert("Hello");
}, 3000);

The above code will execute alert("Hello"); every 3 seconds.


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


As someone else pointed out, the Web Audio API has a better timer.

But in general, if these events happen consistently, how about you put them all on the same timer? I'm thinking about how a step sequencer works.

Practically, could it looks something like this?

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

According to your requirement

just show me a basic example of using setTimeout to loop something

we have following example which can help you

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

function timerCycle() {
if (stoptime == false) {
sec = parseInt(sec);
min = parseInt(min);
hr = parseInt(hr);
sec = sec + 1;
if (sec == 60) {
min = min + 1;
sec = 0;
}


if (min == 60) {
hr = hr + 1;
min = 0;
sec = 0;
}


if (sec < 10 || sec == 0) {
sec = "0" + sec;
}
if (min < 10 || min == 0) {
min = "0" + min;
}
if (hr < 10 || hr == 0) {
hr = "0" + hr;
}


timer.innerHTML = hr + " : " + min + " : " + sec;


setTimeout(timerCycle, 1000);
}
}


function startTimer() {
if (stoptime == true) {
stoptime = false;
timerCycle();
}
}


function stopTimer() {
if (stoptime == false) {
stoptime = true;
}
}
function resetTimer() {
hr = 0;
min = 0;
sec = 0;
stopTimer();
timer.innerHTML = "00:00:00";
}