2016-11-22 2 views
1

У меня долго работающая задача в JavaScript, которую я разбиваю на куски с серией вложенных setTimeout(processChunk, 0), аналогично тому, как описано here. Однако для каждого вызова setTimeout добавляет дополнительную задержку 4 мс или более. Это поведение well known и меняется в разных браузерах.Избегайте принудительной задержки setTimeout при разрыве длинных JavaScript-задач

Когда я пытаюсь сохранить время обработки каждого блока в течение 50 мс или меньше, эти дополнительные задержки увеличивают общее время обработки не менее чем на 10%.

Мой вопрос:: Могу ли я избежать дополнительной задержки (и, следовательно, повысить скорость обработки), сохраняя обратную совместимость с браузерами ES3 и старыми браузерами IE?

ответ

2

Существует простой способ обхода проблемы. Поскольку минимальная задержка setTimeout измеряется с момента установки таймера, не забудьте установить таймеры не менее 10 – 15 мс, прежде чем обрабатывать каждый кусок. Когда задано несколько 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 мс или более для каждого блока (около 40 мс или более для 10 кусков, как показано ниже) при условии, что каждый кусок занимает не менее 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); 
 
});

+0

Вы ответили на свой вопрос, в то же время вы задали этот вопрос. – Erevald

+0

@ Эвералд, это верно. [Это рекомендуется SO] (https://stackoverflow.blog/2011/07/its-ok-to-ask-and-answer-your-own-questions/). Недавно я боролся с этим вопросом и нашел решение, которое я хотел бы документировать и делиться. Поделившись, кто-то может даже предложить лучшее решение. –