Javascript, setTimeout-Schleifen?


88

Ich arbeite an einem Musikprogramm, bei dem mehrere Javascript-Elemente mit einem anderen synchron sein müssen. Ich habe setInterval verwendet, das anfangs sehr gut funktioniert, aber im Laufe der Zeit werden die Elemente allmählich nicht mehr synchron, was mit einem Musikprogramm schlecht ist.

Ich habe online gelesen, dass setTimeout genauer ist, und Sie können setTimeout-Schleifen irgendwie haben, aber ich habe keine generische Version gefunden, die veranschaulicht, wie dies möglich ist. Könnte mir jemand ein einfaches Beispiel für die Verwendung von setTimeout zeigen, um etwas auf unbestimmte Zeit zu schleifen.

Danke dir. Wenn es alternativ möglich ist, mit setInterval oder einer anderen Funktion synchronere Ergebnisse zu erzielen, lassen Sie es mich bitte wissen.

BEARBEITEN:

Grundsätzlich habe ich einige Funktionen wie diese:

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

Es funktioniert anfangs super gut, aber im Laufe von ungefähr einer Minute werden die Sounds merklich nicht mehr synchron, da ich gelesen habe, dass dies mit setInterval geschieht. Ich habe gelesen, dass setTimeout konsistenter genau sein kann.


2
Warum posten Sie nicht einen Code, der uns zeigt, was Sie erreichen möchten, und wir können Ihnen bessere Antworten geben.
Andy

1
Ich habe online gelesen, dass setTimeout genauer ist : Wo hast du das gelesen? Fügen Sie einen Link hinzu. Ich gehe davon aus, dass dies wahrscheinlich der Fall ist. setTimeoutSie können berechnen, wie lange die Verzögerung tatsächlich gedauert hat, um die Zeit für das nächste Timeout anzupassen.
Matt Burland

2
Was ist mit requestAnimationFrame? Sie müssen nur die Zeit angeben, zu der das Audio bei jedem requestAnimationFrameRückruf ausgeführt wird.
Jasper

5
Keiner der beiden Timertypen ist wirklich garantiert präzise. Die angegebenen Millisekunden sind nur eine minimale Wartezeit, die Funktion kann jedoch später noch aufgerufen werden. Wenn Sie versuchen, mehrere Intervalle zu koordinieren, konsolidieren Sie stattdessen ein Intervall, um das Intervall zu steuern.
Jonathan Lonowski

1
Wenn Sie Musik wirklich mit etwas auf dem Bildschirm synchronisieren möchten, müssen Sie beim Aktualisieren des DOM auf den zeitlichen Fortschritt des Audios verweisen. Andernfalls werden die Dinge die meiste Zeit nicht mehr synchron sein.
Jasper

Antworten:


178

Sie können eine setTimeoutSchleife mithilfe der Rekursion erstellen :

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

Das Problem mit setInterval()und setTimeout()ist, dass es keine Garantie gibt, dass Ihr Code in der angegebenen Zeit ausgeführt wird. Indem setTimeout()Sie es rekursiv verwenden und aufrufen, stellen Sie sicher, dass alle vorherigen Vorgänge innerhalb des Zeitlimits abgeschlossen sind, bevor die nächste Iteration des Codes beginnt.


1
Was ist der Unterschied zwischen dieser Methode und der Verwendung setInterval?
Jasper

4
Das Problem bei diesem Ansatz ist, dass, wenn die Funktion länger als 1000 ms dauert, alles schief geht. Es wird nicht garantiert, dass dies alle 1000 ms ausgeführt wird. setInterval ist.
TJC

@TJC: Das hängt sehr davon ab, was Sie genau erreichen wollen. Es ist möglicherweise wichtiger, dass die vorherige Funktion vor der nächsten Iteration abgeschlossen wird, oder auch nicht.
Matt Burland

4
@TJC Richtig, aber wenn Ihre vorherigen Vorgänge vor der setInterval()erneuten Ausführung nicht abgeschlossen sind , können Ihre Variablen und / oder Daten sehr schnell nicht mehr synchron sein. Wenn ich zum Beispiel nach Daten suche und der Server länger als 1 Sekunde braucht, um zu antworten, hätte die Verwendung setInterval()meiner vorherigen Daten die Verarbeitung nicht abgeschlossen, bevor mein nächster Vorgang fortgesetzt wurde. Bei diesem Ansatz kann nicht garantiert werden, dass Ihre Funktion jede Sekunde gestartet wird. Es ist jedoch garantiert, dass Ihre vorherigen Daten vor Beginn des nächsten Intervalls vollständig verarbeitet wurden.
War10ck

1
@ War10ck, in einer musikbasierten Umgebung, ging ich davon aus, dass es nicht zum Festlegen von Variablen oder Ajax-Aufrufen verwendet werden würde, wenn die Reihenfolge wichtig war.
TJC

28

Nur zur Ergänzung. Wenn Sie eine Variable übergeben und iterieren müssen, können Sie dies wie folgt tun:

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

Ausgabe:

1
2
3
...
9
10

Eine Zeile pro Sekunde.


12

Da keine der beiden Zeiten sehr genau sein wird, besteht eine Möglichkeit, setTimeoutetwas genauer zu sein, darin, zu berechnen, wie lange die Verzögerung seit der letzten Iteration gedauert hat, und dann die nächste Iteration entsprechend anzupassen. Beispielsweise:

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

Wenn Ihr Code also zum ersten Mal (mindestens) 1000 ms wartet, wenn er ausgeführt wird, ist es möglicherweise etwas spät, z. B. 1046 ms. Wir subtrahieren also 46 ms von unserer Verzögerung für den nächsten Zyklus und die nächste Verzögerung ist nur 954 ms. Dies verhindert nicht, dass der Timer zu spät ausgelöst wird (was zu erwarten ist), hilft Ihnen jedoch dabei, das Auffüllen der Verzögerungen zu verhindern. (Hinweis: Möglicherweise möchten Sie überprüfen, ob thisDelay < 0die Verzögerung mehr als doppelt so hoch war wie Ihre Zielverzögerung, und Sie haben einen Zyklus verpasst - bis zu Ihnen, wie Sie mit diesem Fall umgehen möchten.)

Dies wird Ihnen wahrscheinlich nicht helfen, mehrere Timer synchron zu halten. In diesem Fall möchten Sie vielleicht herausfinden, wie Sie sie alle mit demselben Timer steuern können.

Wenn Sie sich also Ihren Code ansehen, sind alle Ihre Verzögerungen ein Vielfaches von 500, sodass Sie so etwas tun können:

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 Ordentlicher Ansatz. Ich habe nie daran gedacht, die Zeitüberschreitung des nächsten Laufs auszugleichen. Das würde Sie wahrscheinlich so nah wie möglich an eine genaue Messung bringen, wenn man bedenkt, dass JavaScript kein Multithreading ist und nicht garantiert in einem konsistenten Zeitintervall ausgelöst wird.
War10ck

Das ist toll! Ich werde es versuchen und dich wissen lassen, wie es geht. Mein Code ist
riesig,

Bedenken Sie: Wenn dieser Code in einer festen (wie oben korrigierten) Zeitschleife ausgelöst wird, ist dies möglicherweise die falsche Art, über das Problem nachzudenken. Es könnte sein, dass Sie tatsächlich jede Intervalllänge auf "Das nächste Mal, wenn etwas gespielt werden soll, abzüglich der aktuellen Zeit" einstellen sollten .
Mwardm

Sie haben die bessere Version der Lösung erstellt, die ich gerade aufgeschrieben habe. Ich finde es gut, dass Ihre Variablennamen die Sprache der Musik verwenden und dass Sie versuchen, die auftretenden Verzögerungen zu bewältigen, die ich nicht einmal versuche.
Miguel Valencia

8

Der beste Weg, um mit dem Audio-Timing umzugehen, ist mit der Web-Audio-API. Sie verfügt über eine separate Uhr, die genau ist, unabhängig davon, was im Haupt-Thread passiert. Hier gibt es eine großartige Erklärung, Beispiele usw. von Chris Wilson:

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

Schauen Sie sich auf dieser Website nach weiteren Web-Audio-APIs um. Sie wurde entwickelt, um genau das zu tun, wonach Sie suchen.


Ich wünschte wirklich, mehr Leute würden dies unterstützen. Die Antworten setTimeoutreichen von unzureichend bis schrecklich überkomplex. Die Verwendung einer nativen Funktion scheint eine viel bessere Idee zu sein. Wenn die API Ihren Zweck nicht erfüllt, würde ich empfehlen, nach einer zuverlässigen Planungsbibliothek von Drittanbietern zu suchen.
drei

3

Verwenden setInterval()

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

Das obige wird alert("Hello");alle 3 Sekunden ausgeführt.


1

Ich benutze diesen Weg im Arbeitsleben: "Vergiss gemeinsame Schleifen" in diesem Fall und benutze diese Kombination von "setInterval" beinhaltet "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: Verstehe, dass das wirkliche Verhalten von (setTimeOut): Sie werden alle zur gleichen Zeit starten "Die drei bla bla bla werden im selben Moment herunterzählen", also mache eine andere Zeitüberschreitung, um die Ausführung zu arrangieren.

PS 2: Das Beispiel für eine Zeitschleife, aber für eine Reaktionsschleife können Sie Ereignisse verwenden, versprechen asynchrones Warten.


1

setTimeout- Schleifenproblem mit Lösung

// 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
Irgendeine Idee, warum es sich zwischen var und let anders verhält?
Jay

@ Jay ... Der Unterschied zwischen ihnen besteht darin, dass var einen Funktionsbereich hat und let einen Blockbereich hat. Weitere Informationen erhalten
Vahid Akhtar

1

Entsprechend Ihrer Anforderung

Zeigen Sie mir einfach ein einfaches Beispiel für die Verwendung von setTimeout, um etwas zu schleifen

Wir haben folgendes Beispiel, das Ihnen helfen kann

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


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
Eine Erklärung Ihrer Lösung wäre sehr dankbar!
Tonne

0

Wie bereits erwähnt, verfügt die Web-Audio-API über einen besseren Timer.

Aber wenn diese Ereignisse konsistent auftreten, wie wäre es dann, wenn Sie sie alle auf den gleichen Timer setzen? Ich denke darüber nach, wie ein Step-Sequenzer funktioniert.

Könnte es praktisch so aussehen?

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

Ich denke, es ist besser, am Ende der Funktion eine Zeitüberschreitung zu verursachen.

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

Diese drei Funktionen sind erforderlich, da Variablen in der zweiten Funktion jedes Mal mit dem Standardwert überschrieben werden. Wenn der Programmzeiger das setTimeout erreicht, ist bereits ein Schritt berechnet. Dann braucht nur der Bildschirm etwas Zeit.


-2

Verwenden Sie let anstelle von var im Code:

for(let i=1;i<=5;i++){setTimeout(()=>{console.log(i)},1000);}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.