วิธีทำให้ "setInterval" ทำงานในการซิงค์มากขึ้นหรือจะใช้ "setTimeout" แทนได้อย่างไร


91

ฉันกำลังทำงานกับโปรแกรมเพลงที่ต้องใช้องค์ประกอบ 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หรือแม้แต่ฟังก์ชันอื่นโปรดแจ้งให้เราทราบ


2
ทำไมคุณไม่โพสต์โค้ดที่แสดงให้เราเห็นว่าคุณต้องการบรรลุอะไรและเราสามารถให้คำตอบที่ดีกว่า
Andy

1
ฉันอ่านออนไลน์แล้วว่า setTimeout แม่นยำกว่า : คุณอ่านที่ไหน รวมลิงค์ ฉันสมมติว่าอาจเป็นกรณีที่setTimeoutคุณสามารถคำนวณระยะเวลาที่ล่าช้าคือการปรับเวลาสำหรับการหมดเวลาครั้งต่อไป
Matt Burland

2
เกี่ยวกับอะไรrequestAnimationFrame? คุณต้องอ้างอิงเวลาที่เสียงอยู่ในแต่ละครั้งที่requestAnimationFrameเรียกกลับของคุณ
Jasper

5
ไม่มีการรับประกันว่าตัวจับเวลาทั้งสองประเภทนั้นแม่นยำจริงๆ มิลลิวินาทีที่ระบุเป็นเพียงเวลารอขั้นต่ำ แต่ยังสามารถเรียกใช้ฟังก์ชันนี้ได้ในภายหลัง หากคุณกำลังพยายามประสานช่วงเวลาหลายช่วงให้ลองรวมเป็นช่วงเวลาเดียวแทนการควบคุมช่วง
Jonathan Lonowski

1
หากคุณต้องการซิงค์เพลงกับบางสิ่งบนหน้าจอจริงๆคุณจำเป็นต้องอ้างอิงเวลาที่ผ่านเสียงเมื่อคุณอัปเดต DOM มิฉะนั้นสิ่งต่างๆจะไม่ตรงกันเกือบตลอดเวลา
Jasper

คำตอบ:


183

คุณสามารถสร้าง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อย่างไร?
Jasper

4
ปัญหาเกี่ยวกับแนวทางนี้คือถ้าฟังก์ชันใช้เวลาดำเนินการนานกว่า 1,000 มิลลิวินาทีทุกอย่างจะเกิดขึ้น ไม่รับประกันว่าจะดำเนินการทุก ๆ 1000ms setInterval คือ.
TJC

@TJC: มากขึ้นอยู่กับสิ่งที่คุณพยายามบรรลุ อาจสำคัญกว่าที่ฟังก์ชันก่อนหน้านี้จะต้องดำเนินการให้เสร็จสิ้นก่อนการทำซ้ำครั้งถัดไปหรืออาจจะไม่
Matt Burland

5
@TJC ถูกต้อง แต่ถ้าการดำเนินการก่อนหน้านี้ของคุณไม่สมบูรณ์ก่อนที่จะsetInterval()ดำเนินการใหม่ตัวแปรและ / หรือข้อมูลของคุณอาจไม่ซิงค์กันอย่างรวดเร็ว ตัวอย่างเช่นหากฉันกำลังแง้มข้อมูลและเซิร์ฟเวอร์ใช้เวลาในการตอบกลับนานกว่า 1 วินาทีการใช้setInterval()ข้อมูลก่อนหน้าของฉันจะยังประมวลผลไม่เสร็จก่อนที่จะดำเนินการต่อไป ด้วยวิธีนี้ไม่รับประกันว่าฟังก์ชันของคุณจะเริ่มทำงานทุกวินาที อย่างไรก็ตามขอรับประกันว่าข้อมูลก่อนหน้าของคุณจะประมวลผลเสร็จสิ้นก่อนช่วงเวลาถัดไปจะเริ่มขึ้น
War10ck

1
@ War10ck จากสภาพแวดล้อมที่เป็นดนตรีฉันคิดว่ามันจะไม่ถูกใช้เพื่อตั้งค่าตัวแปรหรือการเรียก ajax ที่ลำดับมีความสำคัญ
TJC

31

เพื่อเสริม. หากคุณต้องการส่งผ่านตัวแปรและวนซ้ำคุณสามารถทำได้ดังนี้:

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

เอาท์พุต:

1
2
3
...
9
10

หนึ่งบรรทัดต่อวินาที


12

เนื่องจากไม่มีเวลาใดที่จะแม่นยำมากวิธีหนึ่งที่จะใช้setTimeoutเพื่อให้แม่นยำยิ่งขึ้นคือการคำนวณระยะเวลาการหน่วงเวลานับตั้งแต่การวนซ้ำครั้งสุดท้ายจากนั้นจึงปรับการทำซ้ำครั้งต่อไปตามความเหมาะสม ตัวอย่างเช่น:

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

ดังนั้นในครั้งแรกที่จะรอ (อย่างน้อย) 1,000 มิลลิวินาทีเมื่อโค้ดของคุณถูกเรียกใช้งานอาจล่าช้าเล็กน้อยพูดว่า 1046 มิลลิวินาทีดังนั้นเราจึงลบ 46 มิลลิวินาทีออกจากความล่าช้าของเราสำหรับรอบถัดไปและความล่าช้าถัดไปจะเป็น เพียง 954 มิลลิวินาที วิธีนี้จะไม่หยุดตัวจับเวลาไม่ให้ทำงานช้า (ซึ่งเป็นไปตามคาด) แต่จะช่วยให้คุณหยุดความล่าช้าจากการเติมเงินได้ (หมายเหตุ: คุณอาจต้องการตรวจสอบthisDelay < 0ซึ่งหมายความว่าความล่าช้านั้นมากกว่าความล่าช้าเป้าหมายของคุณมากกว่าสองเท่าและคุณพลาดรอบ - ขึ้นอยู่กับคุณว่าคุณต้องการจัดการกับกรณีนั้นอย่างไร)

แน่นอนว่านี่อาจไม่ช่วยให้คุณซิงค์ตัวจับเวลาหลายตัวได้ในกรณีนี้คุณอาจต้องการหาวิธีควบคุมทั้งหมดด้วยตัวจับเวลาเดียวกัน

ดังนั้นเมื่อดูรหัสของคุณความล่าช้าทั้งหมดของคุณคือผลคูณของ 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

นี่มันเยี่ยมมาก! ฉันจะลองทำดูและบอกให้คุณรู้ว่ามันเป็นอย่างไร รหัสของฉันมีขนาดใหญ่ดังนั้นอาจใช้เวลาสักครู่
user3084366

ลองพิจารณา: การมีทริกเกอร์รหัสนี้ในการวนรอบเวลาคงที่ (แม้ว่าจะได้รับการแก้ไขแล้วก็ตาม) อาจเป็นวิธีที่ผิดในการคิดถึงปัญหา อาจเป็นไปได้ว่าคุณควรตั้งค่าความยาวของช่วงเวลาแต่ละช่วงเป็น "ครั้งต่อไป - บางสิ่ง - เนื่องจากจะเล่นลบเวลาปัจจุบัน"
mwardm

คุณสร้างโซลูชันเวอร์ชันที่ดีกว่าที่ฉันเพิ่งเขียนลงไป ฉันชอบที่ชื่อตัวแปรของคุณใช้ภาษาจากเพลงและคุณพยายามจัดการกับความล่าช้าที่ก่อตัวขึ้นซึ่งฉันไม่ได้พยายาม
มิเกลวาเลนเซีย

9

วิธีที่ดีที่สุดในการจัดการกับจังหวะเสียงคือ Web Audio Api ซึ่งมีนาฬิกาแยกต่างหากที่แม่นยำไม่ว่าจะเกิดอะไรขึ้นในเธรดหลักก็ตาม มีคำอธิบายตัวอย่างและอื่น ๆ ที่ยอดเยี่ยมจาก Chris Wilson ที่นี่:

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

ดูไซต์นี้เพื่อดู Web Audio 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" รวมถึง "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: เข้าใจว่าพฤติกรรมที่แท้จริงของ (setTimeOut): พวกเขาทั้งหมดจะเริ่มต้นในเวลาเดียวกัน "บลาบลาบลาทั้งสามจะเริ่มนับถอยหลังในช่วงเวลาเดียวกัน" ดังนั้นให้เว้นระยะเวลาที่แตกต่างกันเพื่อจัดการการดำเนินการ

ป.ล. 2: ตัวอย่างของการวนรอบเวลา แต่สำหรับลูปปฏิกิริยาคุณสามารถใช้เหตุการณ์ได้สัญญาว่า async รอ ..


1

ปัญหาsetTimeout loop พร้อมวิธีแก้ไข

// 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?
Jay

1
@Jay ... ความแตกต่างระหว่างพวกเขาคือ var คือฟังก์ชันที่กำหนดขอบเขตและปล่อยให้เป็นขอบเขตบล็อก สำหรับคำชี้แจงเพิ่มเติมสามารถเข้าไปที่medium.com/@josephcardillo/…
Vahid Akhtar

1

ตามที่คนอื่นกล่าวไว้ Web Audio API มีตัวจับเวลาที่ดีกว่า

แต่โดยทั่วไปแล้วหากเหตุการณ์เหล่านี้เกิดขึ้นอย่างต่อเนื่องคุณจะตั้งเวลาทั้งหมดได้อย่างไร ฉันกำลังคิดเกี่ยวกับวิธีการจัดลำดับขั้นตอนทำงานอย่างไร

ในทางปฏิบัติมันจะมีลักษณะเช่นนี้หรือไม่?

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
คำอธิบายเกี่ยวกับการแก้ปัญหาของคุณจะได้รับการชื่นชมมาก!
ตัน

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

ฟังก์ชันทั้งสามนี้จำเป็นเนื่องจาก vars ในฟังก์ชันที่สองจะถูกเขียนทับด้วยค่าเริ่มต้นทุกครั้ง เมื่อตัวชี้โปรแกรมไปถึง setTimeout ขั้นตอนหนึ่งจะถูกคำนวณแล้ว จากนั้นเพียงแค่หน้าจอต้องใช้เวลาเล็กน้อย


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.