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

最初は非常にうまく機能しますが、約1分のうちに、setIntervalで発生することを読んだため、サウンドの同期が著しくずれます。setTimeoutの方が一貫して正確である可能性があることを読みました。


2
達成したいことを示すコードを投稿してみませんか。より良い回答を提供できます。
アンディ

1
私はsetTimeoutがより正確であることをオンラインで読みました:どこでそれを読みましたか?リンクを含めます。おそらくsetTimeout、遅延が実際にどのくらいの長さであったかを計算して、次のタイムアウトの時間を調整できる場合だと思います。
マットバーランド2014

2
どうrequestAnimationFrameですか?requestAnimationFrameコールバックが実行されるたびに、オーディオの時刻を参照する必要があります。
ジャスパー

5
どちらのタイプのタイマーも、正確であることが実際に保証されているわけではありません。指定されたミリ秒は最小待機時間ですが、関数は後で呼び出すことができます。複数の間隔を調整しようとしている場合は、代わりに1つに統合して、間隔を制御してみてください。
Jonathan Lonowski 2014

1
本当に音楽を画面上の何かに同期させたい場合は、DOMを更新するときにオーディオの時間経過を参照する必要があります。そうしないと、ほとんどの場合、同期がとれなくなります。
ジャスパー

回答:


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:それはあなたが何を達成しようとしているのかによって大きく異なります。前の関数が次の反復の前に完了することがより重要な場合もあれば、そうでない場合もあります。
マットバーランド2014

4
@TJC正解ですが、setInterval()再実行する前に以前の操作が完了していない場合、変数やデータが非常に速く同期しなくなる可能性があります。たとえば、データを取得しているときに、サーバーが応答するのに1秒以上かかる場合setInterval()、前のデータを使用しても、次の操作を続行する前に処理が完了していません。このアプローチでは、関数が毎秒開始されるとは限りません。ただし、次の間隔が開始される前に、前のデータの処理が終了することが保証されています。
War10ck 2014

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

1秒あたり1行。


12

どちらの時間が非常に正確setTimeoutになることはないので、もう少し正確にするために使用する1つの方法は、最後の反復からの遅延の長さを計算し、必要に応じて次の反復を調整することです。例えば:

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遅延が目標遅延の2倍以上であり、サイクルを逃したことを意味するかどうかを確認することをお勧めします-その場合の処理​​方法はあなた次第です)。

もちろん、これはおそらく複数のタイマーの同期を維持するのに役立ちません。その場合、同じタイマーでそれらすべてを制御する方法を理解する必要があるかもしれません。

したがって、コードを見ると、すべての遅延は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 2014

これは素晴らしい!これを試してみて、どうなるかをお知らせします。私のコードは巨大なので、おそらくしばらく時間がかかります
user3084366 2014

考えてみてください。このコードを固定された(上記のように修正されたとしても)タイムループでトリガーすることは、実際には問題について考える間違った方法である可能性があります。実際には、各間隔の長さを「再生予定の次回の何かから現在の時間を引いたもの」に設定する必要がある可能性があります。
mwardm

あなたは私が書き留めたソリューションのより良いバージョンを作成しました。私はあなたの変数名が音楽からの言語を使用していること、そしてあなたが蓄積する遅延を管理しようとしていることを気に入っていますが、私は試していません。
ミゲルバレンシア

8

オーディオタイミングを処理する最良の方法は、Web Audio Apiを使用することです。これには、メインスレッドで何が起こっているかに関係なく正確な別個のクロックがあります。ここにクリスウィルソンからの素晴らしい説明、例などがあります:

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

より多くのWebAudio 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」のこの組み合わせを使用します。

    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)の実際の動作を理解してください:それらはすべて同時に開始されます「3つのbla bla blaは同じ瞬間にカウントダウンを開始します」ので、実行を調整するために異なるタイムアウトを作成します。

PS 2:タイミングループの例ですが、リアクションループの場合はイベントを使用でき、asyncawaitを約束します。


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 AudioAPIにはより優れたタイマーがあります。

しかし、一般的に、これらのイベントが一貫して発生する場合は、それらすべてを同じタイマーに設定するのはどうですか?ステップシーケンサーのしくみを考えています。

実際には、このように見えるでしょうか?

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

2番目の関数の変数は毎回デフォルト値で上書きされるため、この3つの関数が必要です。プログラムポインタがsetTimeoutに達すると、1ステップがすでに計算されています。次に、画面だけに少し時間がかかります。


-2

コードでvarの代わりにletを使用します:

for(let i=1;i<=5;i++){setTimeout(()=>{console.log(i)},1000);}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.