Các vòng lặp Javascript, setTimeout?


91

Vì vậy, tôi đang làm việc trên một chương trình âm nhạc yêu cầu nhiều phần tử javascript để đồng bộ với một phần tử khác. Tôi đã sử dụng setInterval ban đầu hoạt động thực sự tốt, tuy nhiên theo thời gian, các yếu tố dần trở nên không đồng bộ với một chương trình âm nhạc là không tốt.

Tôi đã đọc trực tuyến rằng setTimeout chính xác hơn và bạn có thể có các vòng lặp setTimeout bằng cách nào đó, tuy nhiên tôi chưa tìm thấy phiên bản chung minh họa cách điều này có thể thực hiện được. Ai đó có thể chỉ cho tôi một ví dụ cơ bản về việc sử dụng setTimeout để lặp lại một cái gì đó vô thời hạn không.

Cảm ơn bạn. Ngoài ra, nếu có cách nào để đạt được kết quả đồng bộ hơn với setInterval hoặc thậm chí là một hàm khác, vui lòng cho tôi biết.

BIÊN TẬP:

Về cơ bản tôi có một số chức năng như sau:

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

Ban đầu nó hoạt động rất tốt nhưng trong khoảng một phút, âm thanh trở nên không đồng bộ đáng kể khi tôi đọc xảy ra với setInterval, tôi đã đọc rằng setTimeout có thể chính xác hơn.


2
Tại sao bạn không đăng một số mã cho chúng tôi thấy những gì bạn muốn đạt được và chúng tôi có thể cung cấp cho bạn câu trả lời tốt hơn.
Andy ngày

1
Tôi đã đọc trực tuyến rằng setTimeout chính xác hơn : Bạn đã đọc nó ở đâu? Bao gồm một liên kết. Tôi giả định rằng đó có thể là một trường hợp setTimeoutmà bạn có thể tính toán khoảng thời gian trì hoãn thực sự là bao lâu để điều chỉnh thời gian cho lần chờ tiếp theo.
Matt Burland

2
Về requestAnimationFramethì sao? Bạn chỉ cần tham chiếu thời gian có âm thanh mỗi khi requestAnimationFramecuộc gọi lại của bạn chạy.
Jasper

5
Không loại bộ đếm thời gian nào thực sự được đảm bảo chính xác. Phần nghìn giây cho trước chỉ là thời gian chờ tối thiểu, nhưng hàm vẫn có thể được gọi sau đó. Nếu bạn đang cố gắng phối hợp nhiều khoảng thời gian, thay vào đó hãy thử hợp nhất thành một, kiểm soát khoảng thời gian.
Jonathan Lonowski

1
Nếu bạn thực sự muốn đồng bộ hóa nhạc với thứ gì đó trên màn hình, bạn cần tham khảo tiến trình thời gian thông qua âm thanh khi cập nhật DOM. Nếu không, mọi thứ sẽ không đồng bộ trong hầu hết thời gian.
Jasper

Câu trả lời:


181

Bạn có thể tạo một setTimeoutvòng lặp bằng cách sử dụng đệ quy:

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

Vấn đề với setInterval()setTimeout()là không có gì đảm bảo mã của bạn sẽ chạy trong thời gian được chỉ định. Bằng cách sử dụng setTimeout()và gọi nó một cách đệ quy, bạn đang đảm bảo rằng tất cả các hoạt động trước đó bên trong thời gian chờ đều hoàn tất trước khi lần lặp tiếp theo của mã bắt đầu.


1
Sự khác biệt giữa phương pháp này và cách sử dụng là setIntervalgì?
Jasper

4
vấn đề với cách tiếp cận này là nếu hàm mất hơn 1000 mili giây để hoàn thành, thì tất cả sẽ hoạt động. điều này không được đảm bảo thực hiện mỗi 1000ms. setInterval là.
TJC ngày

@TJC: Điều đó phụ thuộc rất nhiều vào chính xác những gì bạn đang cố gắng đạt được. Điều quan trọng hơn là chức năng trước đó hoàn thành trước lần lặp tiếp theo hoặc có thể không.
Matt Burland

4
@TJC Đúng, nhưng nếu các thao tác trước đó của bạn chưa hoàn tất trước khi setInterval()thực thi lại, thì các biến và / hoặc dữ liệu của bạn có thể mất đồng bộ rất nhanh. Ví dụ: nếu tôi đang xử lý dữ liệu và máy chủ mất hơn 1 giây để phản hồi, thì việc sử dụng setInterval()dữ liệu trước đó của tôi sẽ không được xử lý xong trước khi hoạt động tiếp theo của tôi tiếp tục. Với cách tiếp cận này, không đảm bảo rằng chức năng của bạn sẽ khởi động mỗi giây. Tuy nhiên, đảm bảo rằng dữ liệu trước đó của bạn sẽ được xử lý xong trước khi khoảng thời gian tiếp theo bắt đầu.
War10ck

1
@ War10ck, với một môi trường dựa trên âm nhạc, tôi cho rằng nó sẽ không được sử dụng để đặt các biến hoặc lệnh gọi ajax trong đó thứ tự quan trọng.
TJC ngày

30

Chỉ để bổ sung. Nếu bạn cần truyền một biến và lặp lại nó, bạn có thể làm như sau:

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

Đầu ra:

1
2
3
...
9
10

Một dòng mỗi giây.


12

Cho rằng không phải thời gian nào cũng chính xác, nên có một cách để sử dụng setTimeoutđể chính xác hơn một chút là tính toán khoảng thời gian trì hoãn kể từ lần lặp cuối cùng và sau đó điều chỉnh lần lặp tiếp theo sao cho phù hợp. Ví dụ:

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

Vì vậy, lần đầu tiên nó sẽ đợi (ít nhất) 1000 mili giây, khi mã của bạn được thực thi, nó có thể hơi trễ một chút, chẳng hạn 1046 mili giây, vì vậy chúng tôi trừ 46 mili giây cho độ trễ của chúng tôi cho chu kỳ tiếp theo và độ trễ tiếp theo sẽ là chỉ 954 ms. Điều này sẽ không ngăn bộ đếm thời gian hoạt động muộn (điều đó được mong đợi), nhưng giúp bạn ngăn sự chậm trễ tăng lên. (Lưu ý: bạn có thể muốn kiểm tra xem thisDelay < 0có nghĩa là độ trễ cao hơn gấp đôi độ trễ mục tiêu của bạn và bạn đã bỏ lỡ một chu kỳ - tùy thuộc vào cách bạn muốn xử lý trường hợp đó).

Tất nhiên, điều này có thể sẽ không giúp bạn giữ đồng bộ nhiều bộ hẹn giờ, trong trường hợp đó, bạn có thể muốn tìm cách điều khiển tất cả chúng với cùng một bộ hẹn giờ.

Vì vậy, nhìn vào mã của bạn, tất cả sự chậm trễ của bạn là bội số của 500, vì vậy bạn có thể làm điều gì đó như sau:

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 Cách tiếp cận gọn gàng. Tôi chưa bao giờ nghĩ đến việc bù khoảng thời gian chờ của lần chạy tiếp theo. Điều đó có thể giúp bạn tiến gần đến số đo chính xác nhất có thể vì JavaScript không phải là đa luồng và không được đảm bảo kích hoạt trong một khoảng thời gian nhất quán.
War10ck

Điều đó thật tuyệt! Tôi sẽ thử điều này và cho bạn biết nó diễn ra như thế nào. Mã của tôi là lớn nên nó có thể sẽ mất một thời gian
user3084366

Cân nhắc: Việc kích hoạt mã này trên một vòng thời gian cố định (ngay cả khi đã sửa, như trên) thực sự có thể là cách sai để suy nghĩ về vấn đề. Có thể là bạn thực sự nên đặt độ dài mỗi khoảng thời gian thành "lần sau-điều-gì-đó-là-do-được-phát trừ thời gian hiện tại".
mwardm

Bạn đã tạo ra phiên bản tốt hơn của giải pháp mà tôi vừa viết ra. Tôi thích rằng các tên biến của bạn sử dụng ngôn ngữ từ âm nhạc và bạn cố gắng quản lý sự chậm trễ hình thành, điều mà tôi thậm chí không thử.
Miguel Valencia

8

Cách tốt nhất để xử lý thời gian âm thanh là với Web Audio Api, nó có một đồng hồ riêng chính xác bất kể điều gì đang xảy ra trong chuỗi chính. Có một lời giải thích tuyệt vời, ví dụ, v.v. từ Chris Wilson ở đây:

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

Hãy xem trang web này để biết thêm Web Audio API, nó được phát triển để làm chính xác những gì bạn đang theo đuổi.


Tôi thực sự ước nhiều người sẽ ủng hộ điều này. Các câu trả lời sử dụng setTimeoutphạm vi từ không đủ đến quá mức khủng khiếp. Sử dụng một hàm gốc có vẻ là một ý tưởng tốt hơn nhiều. Nếu API không phục vụ mục đích của bạn, thì tôi khuyên bạn nên cố gắng tìm một thư viện lập lịch của bên thứ ba đáng tin cậy.
ba giờ


3

Theo yêu cầu của bạn

chỉ cho tôi một ví dụ cơ bản về việc sử dụng setTimeout để lặp lại thứ gì đó

chúng tôi có ví dụ sau đây có thể giúp bạn

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

Tôi sử dụng cách này trong cuộc sống công việc: "Quên các vòng lặp chung" trong trường hợp này và sử dụng kết hợp "setInterval" bao gồm "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)
    }

Tái bút: Hãy hiểu rằng hành vi thực sự của (setTimeOut): tất cả chúng sẽ bắt đầu cùng một lúc "ba bla bla bla sẽ bắt đầu đếm ngược trong cùng một thời điểm" vì vậy hãy tạo thời gian chờ khác để sắp xếp việc thực hiện.

PS 2: ví dụ cho vòng lặp thời gian, nhưng đối với vòng lặp phản ứng, bạn có thể sử dụng các sự kiện, hứa không đồng bộ đang chờ đợi ..


1

Sự cố vòng lặp setTimeout với giải pháp

// 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
Bất kỳ ý tưởng tại sao nó hoạt động khác nhau giữa var và let?
Jay

1
@Jay ... Sự khác biệt giữa chúng là var là phạm vi chức năng và let là phạm vi khối. để làm rõ hơn, bạn có thể truy cập medium.com/@josephcardillo/…
Vahid Akhtar

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
Một lời giải thích về giải pháp của bạn sẽ được đánh giá rất cao!
tấn

0

Như ai đó đã chỉ ra, API âm thanh web có bộ đếm thời gian tốt hơn.

Nhưng nói chung, nếu những sự kiện này xảy ra liên tục, bạn đặt tất cả chúng vào cùng một bộ đếm thời gian thì sao? Tôi đang nghĩ về cách hoạt động của một bộ tuần tự .

Thực tế, nó có thể trông giống như thế này?

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

Tôi nghĩ tốt hơn là hết thời gian chờ ở cuối chức năng.

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

Ba chức năng này là cần thiết vì các vars trong chức năng thứ hai sẽ bị ghi đè bằng giá trị mặc định mỗi lần. Khi con trỏ chương trình đến setTimeout, một bước đã được tính toán. Sau đó, màn hình chỉ cần một chút thời gian.


-2

Sử dụng let thay vì var trong mã:

for(let i=1;i<=5;i++){setTimeout(()=>{console.log(i)},1000);}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.