Javascript - как избежать блокировки браузера при выполнении тяжелой работы?
У меня есть такая функция в моем JS script:
function heavyWork(){
for (i=0; i<300; i++){
doSomethingHeavy(i);
}
}
Может быть, "doSomethingHeavy" в порядке сам по себе, но повторение его 300 раз заставляет окно браузера застревать в течение незначительного времени. В Chrome это не такая уж большая проблема, потому что выполняется только одна вкладка; но для Firefox это полная катастрофа.
Есть ли способ сообщить браузеру /JS "успокоиться" и не блокировать все между вызовами doSomethingHeavy?
Ответы
Ответ 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);
}
}
Ответ 3
Вам нужно использовать веб-работников
https://developer.mozilla.org/En/Using_web_workers
Есть много ссылок на веб-работников, если вы ищете в google
Ответ 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.
-
Последняя вещь (возможно) состоит в том, чтобы объединить все методы - с традиционным способом использования функции только один поток. Если вы можете использовать веб-работников, вы можете разделить работу между несколькими. Это должно свести к минимуму время, необходимое для завершения задачи.