2016-11-22 66 views
1

我在JavaScript中有一個長時間運行的任務,我用一系列嵌套的setTimeout(processChunk, 0)分塊,這與here類似。但是,對於每次調用,setTimeout都會增加4 ms或更多的附加延遲。此行爲是well known,並因瀏覽器而異。避免在分解長時間運行的JavaScript任務時強制延遲setTimeout

當我試圖將每個塊的處理時間保持在50 ms或更少時,這些額外的延遲將總處理時間增加至少10%。

我的問題是:我能避免額外的延遲(從而提高處理速度),同時保持向後兼容ES3的瀏覽器和IE舊瀏覽器兼容?

回答

2

這個問題有一個簡單的解決方法。由於setTimeout的最小延遲時間是從定時器設置開始計算的,因此在處理每個塊之前,請務必在15 ms內設置定時器至少10 ms。當設置了幾個setTimeout時,它們排隊並在下一個之後立即被調用,而沒有額外的延遲。這可以通過僅2個活動定時器來完成:

function runLongTask() { 
    var complete = false; 
    function processChunk() { 
    if(!complete) { 
     /* ... process chunk, set complete flag after last chunk ... */ 
     //set new timer 
     setTimeout(processChunk); 
    } else { 
     /* ... code to run on completion ... */ 
    } 
    } 
    //set a timer to start processing 
    setTimeout(processChunk); 
    //set an extra timer to make sure 
    //there are always 2 active timers, 
    //this removes the extra delay provided 
    //that processing each chunk takes longer 
    //than the forced delay 
    setTimeout(processChunk); 
} 

下面是比較變通的辦法,以每塊被處理後,設置新setTimeout的傳統方法一個工作演示。在解決方法中,總是會有一個額外的setTimeout,如果每個塊需要至少4 ms來減少每個塊大約4 ms或更長的處理時間(10塊大約40 ms或更多,如下所示)處理。請注意,該解決方法演示了僅使用2個活動計時器的情況。

function runForAtLeast15ms() { 
 
    var d = (+new Date) + 15; 
 
    while(+new Date < d); 
 
} 
 

 
function testTimeout(repetitions, next, workaround) { 
 
    var startTime = +new Date; 
 

 
    function runner() { 
 
    if(repetitions > 0) { 
 
     //process chunk 
 
     runForAtLeast15ms(); 
 
     //set new timer 
 
     setTimeout(runner); 
 
    } else if(repetitions === 0) { 
 
     //report result to console 
 
     console.log((workaround? 'Workaround' : 'Traditional') + 
 
        ' approach: ' + 
 
        ((+new Date) - startTime) + ' ms'); 
 
     //invoke next() function if provided 
 
     next && next(); 
 
    } 
 
    repetitions--; 
 
    } 
 

 
    setTimeout(runner); 
 

 
    if(workaround){ 
 
    //make sure that there are always 2 
 
    //timers running by setting an extra timer 
 
    //at start 
 
    setTimeout(runner); 
 
    } 
 
} 
 

 
//First: repeat runForAtLeast15ms 10 times 
 
//with repeated setTimeout 
 
testTimeout(10, function(){ 
 
    //Then: repeat runForAtLeast15ms 10 times 
 
    //using a repeated set of 2 setTimeout 
 
    testTimeout(10, false, true); 
 
});

+0

你在你問的問題,同時回答了你自己的問題。 – Erevald

+0

@Everald,是的。 [它受到SO的鼓勵](https://stackoverflow.blog/2011/07/its-ok-to-ask-and-answer-your-own-questions/)。我最近一直在努力解決這個問題,並找到了我想要記錄和分享的解決方案。通過分享,有人甚至可以提供更好的解決方案。 –