Подтвердить что ты не робот

Javascript - как избежать блокировки браузера при выполнении тяжелой работы?

У меня есть такая функция в моем JS script:

function heavyWork(){
   for (i=0; i<300; i++){
        doSomethingHeavy(i);
   }
}

Может быть, "doSomethingHeavy" в порядке сам по себе, но повторение его 300 раз заставляет окно браузера застревать в течение незначительного времени. В Chrome это не такая уж большая проблема, потому что выполняется только одна вкладка; но для Firefox это полная катастрофа.

Есть ли способ сообщить браузеру /JS "успокоиться" и не блокировать все между вызовами doSomethingHeavy?

4b9b3361

Ответ 1

Вы можете вложить свои вызовы внутри вызова setTimeout:

for(...) {
    setTimeout(function(i) {
        return function() { doSomethingHeavy(i); }
    }(i), 0);
}

Это вызывает вызовы doSomethingHeavy для немедленного выполнения, но другие операции JavaScript могут быть заключены между ними.

Лучшее решение состоит в том, чтобы на самом деле браузер открыл новый неблокирующий процесс через Web Workers, но этот HTML5-специфический.

EDIT:

Использование setTimeout(fn, 0) на самом деле занимает много больше нуля миллисекунд - Firefox, например обеспечивает минимальное время ожидания 4 миллисекунды. Лучшим подходом может быть использование setZeroTimeout, который предпочитает postMessage для мгновенного вызова прерывания вызова, но используйте setTimeout как резерв для старых браузеров.

Ответ 2

Вы можете попробовать упаковать каждый вызов функции в setTimeout с таймаутом 0. Это вызовет вызовы в нижней части стека и позволит обозревателю обойтись между ними.

function heavyWork(){
   for (i=0; i<300; i++){
        setTimeout(function(){
            doSomethingHeavy(i);
        }, 0);
   }
}

EDIT: Я только понял, что это не сработает. Значение i будет одинаковым для каждой итерации цикла, вам нужно сделать закрытие.

function heavyWork(){
   for (i=0; i<300; i++){
        setTimeout((function(x){
            return function(){
                doSomethingHeavy(x);
            };
        })(i), 0);
   }
}

Ответ 4

Нам нужно выпустить управление браузером так часто, чтобы избежать монополизации внимания браузера.

Один из способов освобождения элемента управления - использовать setTimeout, который планирует вызывать "обратный вызов" в определенный промежуток времени. Например:

var f1 = function() {
    document.body.appendChild(document.createTextNode("Hello"));
    setTimeout(f2, 1000);
};

var f2 = function() {
    document.body.appendChild(document.createTextNode("World"));
};

Вызов f1 здесь добавит слово hello к вашему документу, расписанию ожидающих вычислений и затем отпустите элемент управления в браузере. В конце концов, будет вызываться f2.

Обратите внимание, что этого недостаточно, чтобы покрасить setTimeout без разбора во всей вашей программе, как если бы это была волшебная пыль пиксела: вам действительно нужно инкапсулировать остальную часть вычисления в обратном вызове. Как правило, setTimeout будет последней вещью в функции, а остальная часть вычислений будет заполнена в обратном вызове.

В вашем конкретном случае код необходимо тщательно преобразовать в следующее:

var heavyWork = function(i, onSuccess) {
   if (i < 300) {
       var restOfComputation = function() {
           return heavyWork(i+1, onSuccess);
       }
       return doSomethingHeavy(i, restOfComputation);          
   } else {
       onSuccess();
   }
};

var restOfComputation = function(i, callback) {
   // ... do some work, followed by:
   setTimeout(callback, 0);
};

который выдает управление браузеру на каждом restOfComputation.

Как еще один конкретный пример этого, см. Как я могу поставить очередь в ряд звуковых HTML5 <audio> звуковые клипы для воспроизведения последовательно?

Усовершенствованные программисты JavaScript должны знать, как это сделать, или же они попадают в проблемы, с которыми вы сталкиваетесь. Вы обнаружите, что если вы используете эту технику, вам придется писать свои программы в своеобразном стиле, где каждая функция, которая может освобождать управление, принимает функцию обратного вызова. Техническим термином для этого стиля является "стиль продолжения прохождения" или "асинхронный стиль".

Ответ 5

Я вижу два пути:

a) Вы можете использовать функцию Html5. Затем вы можете использовать рабочий поток.

b) Вы разделили эту задачу и поставили в очередь сообщение, которое просто делает один вызов одновременно, и итерируя, как долго есть что-то делать.

Ответ 6

function doSomethingHeavy(param){
   if (param && param%100==0) 
     alert(param);
}

(function heavyWork(){
    for (var i=0; i<=300; i++){
       window.setTimeout(
           (function(i){ return function(){doSomethingHeavy(i)}; })(i)
       ,0);
    }
}())

Ответ 7

Был человек, который написал специфическую javascript-библиотеку backgroundtask для выполнения такой тяжелой работы. Вы можете проверить это здесь:

Выполнение фоновой задачи в Javascript

Не использовал это для себя, просто использовал упомянутое использование потоков.

Ответ 8

Вы можете сделать много вещей:

  • оптимизировать циклы - если тяжелые работы имеют какое-то отношение к доступу DOM, см. этот ответ
    • Если функция работает с некоторыми типами необработанных данных, используйте типизированные массивы MSDN MDN
  • метод с setTimeout() называется eteration. Очень полезно.

  • функция, по-видимому, очень прямолинейна для всех нефункциональных языков программирования. JavaScript получает преимущества обратных вызовов SO question.

  • одна новая функция - веб-рабочие MDN MSDN wikipedia.

  • Последняя вещь (возможно) состоит в том, чтобы объединить все методы - с традиционным способом использования функции только один поток. Если вы можете использовать веб-работников, вы можете разделить работу между несколькими. Это должно свести к минимуму время, необходимое для завершения задачи.