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

16

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

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

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

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

@EranZimmerman: я не думаю, что вы можете "спать" в JavaScript. Лучшее, что вы можете сделать, этоsetTimeout каждый звонокdoSomethingHeavy. Rocket Hazmat
Вы можете попробовать спать между звонкамиdoSomethingHeavy, Кроме того, если эта функция имеет дело с коммуникацией с каким-либо сервером, AJAX может быть правильным способом. Eran Zimmerman

Ваш Ответ

8   ответов
1
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);
    }
}())
Нет. В качестве доказательства попробуйте:var test = function() { var f = function() {alert('hi!');}; setTimeout(f, 0); while(true) {} }; test(), В вашей презентации это будет "привет!". Обратите внимание, что это не так. Я пытаюсь указать, что просто добавивsetTimeout() недостаточно призваниеsetTimeout не возвращает управление браузеру: это всего лишь механизм планирования.
@Doooo: Я не думаю, что это правильно, может быть, правильный путь будет0, но я почти уверен, что он переходит к следующему в стеке, пока он ждет.
Код выше неэффективен. setTimeout не волшебным образом сохраняет окружающий вычислительный контекст и не возвращает управление браузеру. Это механизм планирования. Вышеприведенное объяснение неверно.
Я вижу, что вы говорите, и вы правы, речь идет о планировании (как и управление браузером). Проблема в том, что это должно происходить внеdoSoemthingHeavy, если вы не создаете свой собственный стек очереди и не проверяете это с б / вdoSomethingHeavy, Кажется, есть много ответов с тех пор, как я первоначально опубликовал это, поэтому я просто удалю после того, как дал вам некоторое время, чтобы увидеть этот комментарий.
3

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

Один из способов снять контроль - использовать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 будет называться.

Обратите внимание, что этого недостаточно, чтобы посыпатьsetTi,meout без разбора по всей вашей программе, как если бы это была волшебная пыльца пикси: вам действительно нужно инкапсулировать остальную часть вычислений в обратном вызове. Как правило,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 & lt; audio & gt; звуковые клипы, чтобы играть в последовательности?

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

1

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

  1. optimize the loops - if the heavy works has something to do with DOM access see this answer
    • if the function is working with some kind of raw data use typed arrays MSDN MDN
  2. the method with setTimeout() is called eteration. Very usefull.

  3. the function seems to be very straight forward typicall for non-functional programming languages. JavaScript gains advantage of callbacks SO question.

  4. one new feature is web workers MDN MSDN wikipedia.

  5. the last thing ( maybe ) is to combine all the methods - with the traditional way the function is using only one thread. If you can use the web workers, you can divide the work between several. This should minimize the time needed to finish the task.

11

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

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

РЕДАКТИРОВАТЬ: Я только что понял, что это не сработает.i Значение будет одинаковым для каждой итерации цикла, вам нужно сделать замыкание.

function heavyWork(){
   for (i=0; i<300; i++){
        setTimeout((function(x){
            return function(){
                doSomethingHeavy(x);
            };
        })(i), 0);
   }
}
@Teemu: [нужная цитата] РЕДАКТИРОВАТЬ: нашел этоdeveloper.mozilla.org/en/DOM/…
Я только что нашел ту же MDN-ссылку для вас ... Она уже здесь. В последнее время я "перевожу" некоторые старые HTA для использования утилит IE9, и мне приходилось менять каждую задержку по таймауту и интервалу меньше 4 на 4. Если ниже, вызовы не вызываются.
0 больше не является приемлемым значением для задержки, вам необходимо использовать по крайней мере4.
@rocket: см.stackoverflow.com/questions/1776239/…
@Teemu: Согласноspecэто должно произойти автоматически:If the currently running task is a task that was created by the setTimeout() method, and timeout is less than 4, then increase timeout to 4.
2

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

а) Вам разрешено использовать функцию HTML5. Тогда вы можете рассмотреть возможность использования рабочего потока.

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

1

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

Выполнить фоновое задание в Javascript

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

7

Вы должны использовать веб-работников

https://developer.mozilla.org/En/Using_web_workers

Есть много ссылок на веб-работников, если вы ищете в Google

19

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

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

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

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

EDIT:

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

Которые должны бытьsetTimeout((function(i){
Хорошо сделано. Я думаю, что стоит отметить, что браузер будет заморожен во времяdoSomethingHeavyпоэтому, если есть длинные операции, все равно будет зависание, но оно будет 1/300 времени.
Почему setZeroTimeout может быть лучшим подходом? Это немного несправедливо, верно? Цель setTimeout здесь не столько связана с задержкой, сколько дать возможность другому коду запускаться.
Оба метода полезны (для разных видов тяжелой работы) в моем случае. Спасибо! Gadi A
@TobbeBrolin Проблема сsetTimeout(fn, 0) вот (было? сейчас 6 лет)setTimeout имеетminimum время ожидания 4 миллисекунды. Если вам нужно бежатьfn 1000 раз, это верхняя граница 4 дополнительных секунд потерянного времени. Ожидание "ноль" миллисекунды, вы все еще оставляете место для других процессов, чтобы встать между итерациями (вы действительно все еще откладываете на любые задачи ожидания сsetZeroTimeout), но вы не потратили впустую 4 мс, если больше ничего не ожидает очереди задач.

Похожие вопросы