在分解长时间运行的JavaScript任务时,避免强制延迟setTimeout

时间:2016-11-22 22:23:29

标签: javascript performance internet-explorer settimeout

我在JavaScript中有一个长期运行的任务,我用一系列嵌套的setTimeout(processChunk, 0)分块,类似于所描述的here。但是,对于每次调用,setTimeout会增加4 ms或更长的额外延迟。此行为为well known,因浏览器而异。

当我尝试将每个块的处理时间保持在50毫秒或更短时,这些额外的延迟会使总处理时间增加至少10%。

我的问题是:我是否可以避免额外的延迟(从而提高处理速度),同时保持与ES3浏览器和旧IE浏览器的向后兼容性?

1 个答案:

答案 0 :(得分:2)

此问题有一个简单的解决方法。由于setTimeout的最小延迟是从定时器设置时开始测量的,因此请确保在处理每个块之前至少设置10-15 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毫秒或更多的处理时间(对于10个块大约40毫秒或更多,如下所示),前提是每个块都需要处理至少4毫秒。请注意,解决方法演示了仅使用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);
});