`SetInterval`in daha fazla senkronize davranması nasıl sağlanır veya bunun yerine` setTimeout` nasıl kullanılır?


91

Birden çok JavaScript öğesinin bir başkasıyla senkronize olmasını gerektiren bir müzik programı üzerinde çalışıyorum. setIntervalBaşlangıçta gerçekten iyi çalışan kullanıyorum . Bununla birlikte, zamanla öğeler kademeli olarak uyumsuz hale gelir ve bu da bir müzik programında kötüdür.

setTimeoutDaha doğru olanı çevrimiçi okudum ve bir setTimeoutşekilde döngülere sahip olabilirsiniz . Ancak bunun nasıl mümkün olduğunu gösteren genel bir versiyon bulamadım.

Temelde aşağıdaki gibi bazı işlevlerim var:

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

Başlangıçta çok iyi çalışıyor, ancak yaklaşık bir dakika içinde, okuduğum gibi sesler gözle görülür şekilde senkronize olmuyor setInterval. Bunun setTimeoutdaha tutarlı bir şekilde doğru olabileceğini okudum .

Birisi bana bir setTimeoutşeyi süresiz olarak döngüye sokmanın temel bir örneğini gösterebilir mi? Alternatif olarak, setIntervalveya başka bir işlevle daha eşzamanlı sonuçlar elde etmenin bir yolu varsa , lütfen bana bildirin.


2
Neden başarmak istediğinizi bize gösteren bazı kodlar göndermiyorsunuz ve size daha iyi yanıtlar verelim.
Andy

1
İnternette setTimeout'un daha doğru olduğunu okudum: Bunu nerede okudun? Bir bağlantı ekleyin. Muhtemelen bir durum olduğunu varsayıyorum setTimeout, gecikmenin gerçekte ne kadar uzun olduğunu hesaplayabilir ve bir sonraki zaman aşımı için zamanı ayarlayın.
Matt Burland

2
Peki ya requestAnimationFrame? requestAnimationFrameGeri aramanın çalıştığı her seferde sesin olduğu zamana başvurmanız gerekir .
Jasper

5
Her iki zamanlayıcı türünün de kesin olduğu garanti edilmez. Verilen milisaniye sadece minimum bekleme süresidir, ancak fonksiyon daha sonra da çağrılabilir. Birden fazla aralığı koordine etmeye çalışıyorsanız, bunun yerine bir kontrol aralığına birleştirmeyi deneyin.
Jonathan Lonowski

1
Müziği ekrandaki bir şeyle gerçekten senkronize etmek istiyorsanız, DOM'u güncellediğinizde sesteki zaman ilerlemesine başvurmanız gerekir. Aksi takdirde, işler çoğu zaman senkronize olmayacaktır.
Jasper

Yanıtlar:


181

setTimeoutÖzyinelemeyi kullanarak bir döngü oluşturabilirsiniz :

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

İle ilgili sorun setInterval()ve setTimeout()kodunuzun belirtilen zamanda çalışacağına dair hiçbir garanti olmamasıdır. Bunu setTimeout()yinelemeli olarak kullanarak ve çağırarak, zaman aşımı içindeki tüm önceki işlemlerin kodun bir sonraki yinelemesi başlamadan önce tamamlanmasını sağlarsınız.


1
Bu yöntemle kullanım arasındaki fark nedir setInterval?
Jasper

4
Bu yaklaşımla ilgili sorun, işlevin tamamlanması 1000 ms'den uzun sürerse, her şeyin göbek atmasıdır. bunun her 1000 ms'de bir çalışması garanti edilmez. setInterval.
TJC

@TJC: Bu, tam olarak neyi başarmaya çalıştığınıza bağlıdır. Önceki işlevin bir sonraki yinelemeden önce tamamlanması veya tamamlanmaması daha önemli olabilir.
Matt Burland

4
@TJC Doğru, ancak önceki işlemleriniz setInterval()yeniden çalıştırmadan önce tamamlanmadıysa , değişkenleriniz ve / veya verileriniz gerçekten hızlı bir şekilde senkronizasyondan çıkabilir. Örneğin, veri için ajaxing yapıyorsam ve sunucunun yanıt vermesi 1 saniyeden uzun sürerse, bir sonraki işlemime setInterval()devam etmeden önceki verilerimi kullanmak işlemeyi bitirmezdi. Bu yaklaşımla, işlevinizin her saniye başlayacağı garanti edilmez. Ancak, bir sonraki aralık başlamadan önceki verilerinizin işlenmesinin biteceği garanti edilir.
War10ck

1
@ War10ck, müzik tabanlı bir ortam verildiğinde, siparişin önemli olduğu durumlarda değişkenleri veya ajax çağrılarını ayarlamak için kullanılmayacağını varsaydım.
TJC

30

Sadece takviye için. Bir değişkeni geçmeniz ve onu yinelemeniz gerekiyorsa, aynen bunu yapabilirsiniz:

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

Çıktı:

1
2
3
...
9
10

Saniyede bir satır.


12

Her iki zamanın da çok doğru setTimeoutolmayacağı düşünüldüğünde, biraz daha doğru olmanın bir yolu , son yinelemeden bu yana gecikmenin ne kadar olduğunu hesaplamak ve ardından bir sonraki yinelemeyi uygun şekilde ayarlamaktır. Örneğin:

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

Yani ilk kez 1000 ms bekleyecek, kodunuz yürütüldüğünde biraz geç olabilir, mesela 1046 ms, bu yüzden bir sonraki döngü için gecikmemizden 46 ms çıkarıyoruz ve bir sonraki gecikme sadece 954 ms. Bu, zamanlayıcının geç ateşlemesini durdurmaz (bu beklenen bir durumdur), ancak topaklanmadan kaynaklanan gecikmeleri durdurmanıza yardımcı olur. (Not: thisDelay < 0Bu, gecikmenin hedef gecikmenizin iki katından fazla olduğu ve bir döngüyü kaçırdığınız anlamına geldiğini kontrol etmek isteyebilirsiniz - bu durumu nasıl ele almak istediğinize bağlı olarak).

Tabii ki, bu muhtemelen birkaç zamanlayıcıyı senkronize tutmanıza yardımcı olmayacaktır, bu durumda hepsini aynı zamanlayıcıyla nasıl kontrol edeceğinizi bulmak isteyebilirsiniz.

Kodunuza baktığınızda, tüm gecikmeleriniz 500'ün katıdır, bu nedenle şöyle bir şey yapabilirsiniz:

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 Düzgün yaklaşım. Bir sonraki çalıştırmanın zaman aşımı süresini dengelemeyi hiç düşünmedim. JavaScript'in çok iş parçacıklı olmadığını ve tutarlı bir zaman aralığında çalışmasının garanti edilmediğini düşünürsek, bu muhtemelen sizi tam bir ölçüme en yakın ölçüme yaklaştıracaktır.
War10ck

Bu harika! Bunu deneyeceğim ve nasıl gittiğini size bildireceğim.
Kodum

Şunu göz önünde bulundurun: Bu kodun sabit (yukarıdaki gibi düzeltilmiş olsa bile) bir zaman döngüsünde tetiklenmesi, aslında sorun hakkında düşünmenin yanlış yolu olabilir. Aslında her aralık uzunluğunu "bir dahaki sefere oynanacak olan eksi geçerli zaman" olarak ayarlamanız gerekebilir .
mwardm

Az önce yazdığım çözümün daha iyi versiyonunu yaptınız. Değişken adlarınızın müzikten dil kullanması ve biriken gecikmeleri denemen ve yönetmen hoşuma gidiyor ki ben bunu denemiyorum bile.
Miguel Valencia

8

Ses zamanlamasıyla başa çıkmanın en iyi yolu, Web Audio Api'dir, ana iş parçacığında ne olduğuna bakılmaksızın doğru olan ayrı bir saate sahiptir. Burada Chris Wilson'dan harika bir açıklama, örnekler vb. Var:

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

Daha fazla Web Audio API için bu siteye bir göz atın, tam olarak sizin peşinde olduğunuz şeyi yapmak için geliştirilmiştir.


Gerçekten daha fazla insanın buna olumlu oy vermesini diliyorum. setTimeoutYetersizden korkunç derecede aşırı karmaşıklığa kadar değişen yanıtlar . Yerel bir işlevi kullanmak çok daha iyi bir fikir gibi görünüyor. API amacınıza hizmet etmiyorsa, güvenilir bir üçüncü taraf planlama kitaplığı bulmanızı tavsiye ederim.
2019

3

Kullanım setInterval()

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

Yukarıdakiler alert("Hello");her 3 saniyede bir yürütülecektir .


3

İhtiyacınıza göre

sadece bir şeyi döngülemek için setTimeout kullanmanın temel bir örneğini göster

size yardımcı olabilecek aşağıdaki örneğimiz var

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

Çalışma hayatımda şu şekilde kullanıyorum: Bu durumda "Ortak döngüleri unut" ve "setInterval" kombinasyonunu kullanıyorum "setTimeOut" lar:

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

Not: (setTimeOut) 'un gerçek davranışının hepsinin aynı anda başlayacağını anlayın "üç bla bla bla aynı anda geri saymaya başlayacak", bu nedenle yürütmeyi düzenlemek için farklı bir zaman aşımı yapın.

Not 2: zamanlama döngüsü örneği, ancak bir reaksiyon döngüleri için olayları kullanabilirsiniz, asenkron beklemeyi vaat edin ..


1

çözümü ile setTimeout döngü sorunu

// 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 ve let arasında neden farklı davrandığına dair bir fikriniz var mı?
Jay

1
@Jay ... Aralarındaki fark, var işlev kapsamlı ve let'in blok kapsamlı olmasıdır. Daha fazla açıklama için medium.com/@josephcardillo/… adresini ziyaret edebilirsiniz
Vahid Akhtar

1

Başka birinin işaret ettiği gibi, Web Audio API'nin daha iyi bir zamanlayıcısı vardır.

Ama genel olarak, eğer bu olaylar sürekli gerçekleşirse, hepsini aynı zamanlayıcıya koymaya ne dersiniz? Adım sıralayıcının nasıl çalıştığını düşünüyorum.

Pratik olarak böyle bir şey olabilir mi?

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

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
Çözümünüzün açıklaması çok takdir edilecektir!
tonluk

-2

Sanırım işlevin sonunda zaman aşımına uğramak daha iyi.

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

Bu üç işlev gereklidir, çünkü ikinci işlevdeki değişkenler her seferinde varsayılan değer ile değiştirilecektir. Program göstergesi setTimeout'a ulaştığında bir adım zaten hesaplanır. O zaman sadece ekranın biraz zamana ihtiyacı var.


-2

Kodda var yerine let'i kullanın:

for(let i=1;i<=5;i++){setTimeout(()=>{console.log(i)},1000);}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.