There is a straightforward workaround for this issue. Since the minimum delay of setTimeout
is measured from when the timer is set, make sure to set timers at least 10–15 ms before each chunk should be processed. When several setTimeout
are set, they queue up and the next one is invoked immediately after the previous, without the additional delay. This can be done with only 2 active timers:
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);
}
Below is a working demo comparing the workaround approach to the traditional approach of setting a new setTimeout
after each chunk is processed. In the workaround there is always an extra setTimeout
set ahead, reducing the processing time with about 4 ms or more for each chunk (about 40 ms or more for 10 chunks, as demonstrated below), provided that each chunk takes at least 4 ms to process. Note that the workaround demonstrates the use of only 2 active timers.
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);
});