Bagaimana cara membuat `setInterval` berperilaku lebih sinkron, atau cara menggunakan` setTimeout`?

91

Saya sedang mengerjakan program musik yang membutuhkan banyak elemen JavaScript untuk disinkronkan dengan yang lain. Saya telah menggunakan setInterval, yang bekerja dengan sangat baik pada awalnya. Namun, seiring waktu, elemen-elemen tersebut secara bertahap menjadi tidak sinkron yang berdampak buruk dalam program musik.

Saya telah membaca online yang setTimeoutlebih akurat, dan Anda bisa memiliki setTimeoutloop entah bagaimana. Namun, saya belum menemukan versi generik yang menggambarkan bagaimana hal ini dimungkinkan.

Pada dasarnya saya memiliki beberapa fungsi seperti:

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

Ini bekerja dengan sangat baik, pada awalnya, tetapi selama sekitar satu menit, suara menjadi terasa tidak sinkron saat saya membaca setInterval. Saya telah membaca bahwa setTimeoutbisa lebih akurat secara konsisten.

Bisakah seseorang menunjukkan kepada saya contoh dasar penggunaan setTimeoutuntuk mengulang sesuatu tanpa batas? Atau, jika ada cara untuk mendapatkan hasil yang lebih sinkron dengan setIntervalatau bahkan dengan fungsi lain, beri tahu saya.

pengguna3084366
sumber
2
Mengapa Anda tidak memposting beberapa kode yang menunjukkan kepada kami apa yang ingin Anda capai dan kami dapat memberikan jawaban yang lebih baik.
Andy
1
Saya telah membaca secara online bahwa setTimeout lebih akurat : Di mana Anda membacanya? Sertakan link. Saya berasumsi itu mungkin kasus dengan setTimeoutAnda dapat menghitung berapa lama penundaan itu benar-benar menyesuaikan waktu untuk batas waktu berikutnya.
Matt Burland
2
Tentang apa requestAnimationFrame? Anda hanya perlu merujuk waktu audio tersebut setiap kali requestAnimationFramecallback Anda berjalan.
Jasper
5
Tidak ada jenis pengatur waktu yang benar-benar dijamin tepat. Milidetik yang diberikan hanyalah waktu tunggu minimum, tetapi fungsinya masih bisa dipanggil nanti. Jika Anda mencoba mengoordinasikan beberapa interval, coba gabungkan menjadi satu interval kontrol.
Jonathan Lonowski
1
Jika Anda benar-benar ingin menyinkronkan musik ke sesuatu di layar, Anda perlu merujuk waktu kemajuan melalui audio saat Anda memperbarui DOM. Jika tidak, hal-hal akan menjadi tidak sinkron di sebagian besar waktu.
Jasper

Jawaban:

181

Anda dapat membuat setTimeoutloop menggunakan rekursi:

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

Masalahnya dengan setInterval()dan setTimeout()adalah bahwa tidak ada jaminan kode Anda akan berjalan dalam waktu yang ditentukan. Dengan menggunakan setTimeout()dan memanggilnya secara rekursif, Anda memastikan bahwa semua operasi sebelumnya di dalam waktu tunggu selesai sebelum iterasi kode berikutnya dimulai.

War10ck
sumber
1
Apa perbedaan antara metode ini dan penggunaan setInterval?
Jasper
4
Masalah dengan pendekatan ini adalah jika fungsi tersebut membutuhkan waktu lebih dari 1000ms untuk diselesaikan, semuanya akan naik. ini tidak dijamin akan dijalankan setiap 1000ms. setInterval adalah.
TJC
@TJC: Itu sangat tergantung pada apa yang ingin Anda capai. Mungkin lebih penting bahwa fungsi sebelumnya selesai sebelum iterasi berikutnya, atau mungkin tidak.
Matt Burland
4
@TJC Benar, tetapi jika operasi Anda sebelumnya tidak selesai sebelum setInterval()dijalankan ulang, variabel dan / atau data Anda bisa jadi tidak sinkron dengan sangat cepat. Jika saya ajaxing untuk data misalnya, dan server membutuhkan waktu lebih dari 1 detik untuk merespons, menggunakan setInterval()data saya sebelumnya tidak akan menyelesaikan pemrosesan sebelum operasi berikutnya dilanjutkan. Dengan pendekatan ini, tidak ada jaminan bahwa fungsi Anda akan dimulai setiap detik. Namun, dijamin bahwa data Anda sebelumnya akan selesai diproses sebelum interval berikutnya dimulai.
War10ck
1
@ War10ck, mengingat lingkungan berbasis musik, saya berasumsi bahwa itu tidak akan digunakan untuk mengatur variabel atau panggilan ajax di mana urutan penting.
TJC
30

Hanya untuk melengkapi. Jika Anda perlu meneruskan variabel dan mengulanginya, Anda dapat melakukannya seperti ini:

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

Keluaran:

1
2
3
...
9
10

Satu baris per detik.

João Paulo
sumber
12

Mengingat tidak ada waktu yang akan sangat akurat, salah satu cara untuk menggunakan setTimeoutagar sedikit lebih akurat adalah menghitung berapa lama penundaan sejak iterasi terakhir, dan kemudian menyesuaikan iterasi berikutnya yang sesuai. Sebagai contoh:

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

Jadi pertama kali akan menunggu (setidaknya) 1000 ms, ketika kode Anda dieksekusi, mungkin agak terlambat, katakanlah 1046 ms, jadi kami mengurangi 46 ms dari penundaan kami untuk siklus berikutnya dan penundaan berikutnya adalah hanya 954 ms. Ini tidak akan menghentikan pengatur waktu agar tidak bekerja terlambat (yang diharapkan), tetapi membantu Anda menghentikan penundaan agar tidak menumpuk. (Catatan: Anda mungkin ingin memeriksa thisDelay < 0yang artinya penundaan itu lebih dari dua kali lipat penundaan target Anda dan Anda melewatkan satu siklus - terserah Anda bagaimana Anda ingin menangani kasus itu).

Tentu saja, ini mungkin tidak akan membantu Anda menjaga beberapa pengatur waktu tetap sinkron, dalam hal ini Anda mungkin ingin mengetahui cara mengontrol semuanya dengan pengatur waktu yang sama.

Jadi melihat kode Anda, semua penundaan Anda adalah kelipatan 500, jadi Anda dapat melakukan sesuatu seperti ini:

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);
}
Matt Burland
sumber
1
+1 Pendekatan rapi. Saya tidak pernah berpikir untuk mengimbangi periode batas waktu dari proses berikutnya. Itu mungkin akan membuat Anda mendekati pengukuran yang tepat karena Anda bisa mempertimbangkan JavaScript tidak multi-utas dan tidak dijamin akan diaktifkan pada interval waktu yang konsisten.
War10ck
Ini bagus! Saya akan mencoba ini dan memberi tahu Anda bagaimana kelanjutannya. Kode saya sangat besar jadi mungkin perlu beberapa saat
user3084366
Pertimbangkan: Memiliki pemicu kode ini pada putaran waktu tetap (bahkan jika diperbaiki, seperti di atas) mungkin sebenarnya cara yang salah untuk memikirkan masalah. Bisa jadi Anda harus benar-benar menyetel setiap panjang interval ke "waktu-berikutnya-sesuatu-karena-untuk-dimainkan dikurangi waktu-saat ini".
mwardm
Anda membuat versi solusi yang lebih baik yang baru saja saya tulis. Saya suka bahwa nama variabel Anda menggunakan bahasa dari musik dan Anda mencoba dan mengelola penundaan yang menumpuk, yang bahkan tidak saya coba.
Miguel Valencia
8

Cara terbaik untuk menangani pengaturan waktu audio adalah dengan Api Audio Web, ia memiliki jam terpisah yang akurat terlepas dari apa yang terjadi di utas utama. Ada penjelasan bagus, contoh, dll dari Chris Wilson di sini:

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

Silakan melihat-lihat situs ini untuk lebih banyak API Audio Web, itu dikembangkan untuk melakukan apa yang Anda cari.

Bing
sumber
Saya sangat berharap lebih banyak orang akan menyukai ini. Jawaban yang digunakan setTimeoutberkisar dari tidak cukup hingga sangat rumit. Menggunakan fungsi asli sepertinya ide yang jauh lebih baik. Jika API tidak memenuhi tujuan Anda, maka saya akan merekomendasikan untuk mencoba menemukan perpustakaan penjadwalan pihak ketiga yang andal.
threeve
3

Menggunakan setInterval()

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

Di atas akan dijalankan alert("Hello");setiap 3 detik.

Pedro Lobito
sumber
3

Sesuai dengan kebutuhan Anda

tunjukkan saja contoh dasar penggunaan setTimeout untuk mengulang sesuatu

Kami memiliki contoh berikut yang dapat membantu Anda

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

Dupinder Singh
sumber
1

Saya menggunakan cara ini dalam kehidupan kerja: "Lupakan loop umum" dalam kasus ini dan gunakan kombinasi "setInterval" ini termasuk "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: Pahami bahwa perilaku sebenarnya dari (setTimeOut): mereka semua akan mulai dalam waktu yang sama "tiga bla bla bla akan mulai menghitung mundur pada saat yang sama" jadi buat batas waktu yang berbeda untuk mengatur eksekusi.

PS 2: contoh untuk putaran waktu, tetapi untuk loop reaksi Anda dapat menggunakan acara, janji asinkron menunggu ..

Mohamed Abulnasr
sumber
1

masalah loop setTimeout dengan solusi

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

Vahid Akhtar
sumber
1
Adakah ide mengapa itu berperilaku berbeda antara var dan let?
Jay
1
@Jay ... Perbedaan di antara keduanya adalah var adalah cakupan fungsi dan let adalah cakupan blok. untuk klarifikasi lebih lanjut Anda dapat melalui medium.com/@josephcardillo/…
Vahid Akhtar
1

Seperti yang ditunjukkan orang lain, API Audio Web memiliki pengatur waktu yang lebih baik.

Tetapi secara umum, jika peristiwa ini terjadi secara konsisten, bagaimana jika Anda menempatkan semuanya pada timer yang sama? Saya sedang memikirkan tentang cara kerja sequencer langkah .

Secara praktis, mungkinkah terlihat seperti ini?

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
Miguel Valencia
sumber
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);
}

Emosi Kecil
sumber
1
Penjelasan tentang solusi Anda akan sangat dihargai!
ton
-2

Saya pikir lebih baik timeout di akhir fungsi.

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

Ketiga fungsi ini diperlukan karena vars di fungsi kedua akan ditimpa dengan nilai default setiap saat. Ketika penunjuk program mencapai setTimeout, satu langkah sudah dihitung. Maka hanya layar yang membutuhkan sedikit waktu.

BF
sumber
-2

Gunakan let sebagai ganti var dalam kode:

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