Javascript – como evitar o bloqueio do navegador ao fazer trabalhos pesados?

Eu tenho essa function no meu script JS:

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

Talvez “doSomethingHeavy” esteja ok por si só, mas repeti-lo 300 vezes faz com que a janela do navegador fique presa por um tempo não desprezível. No Chrome, não é um grande problema, porque apenas uma guia é afetada; mas para o Firefox é um desastre completo.

Existe alguma maneira de dizer ao navegador / JS para “ter calma” e não bloquear tudo entre as chamadas para doSomethingHeavy?

Você pode aninhar suas chamadas dentro de uma chamada setTimeout :

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

Isso enfileira as chamadas para doSomethingHeavy para execução imediata, mas outras operações JavaScript podem ser colocadas entre elas.

Uma solução melhor é fazer com que o navegador crie um novo processo de não bloqueio via Web Workers , mas isso é específico para HTML5.

EDITAR:

Usar setTimeout(fn, 0) na verdade leva muito mais que zero milissegundos – o Firefox, por exemplo, impõe um tempo de espera mínimo de quatro milissegundos . Uma abordagem melhor pode ser usar setZeroTimeout , que prefere postMessage para invocação de function instantânea e interrompível, mas use setTimeout como um substituto para navegadores mais antigos.

Você pode tentar envolver cada chamada de function em um setTimeout , com um tempo limite de 0. Isso empurrará as chamadas para a parte inferior da pilha e deverá deixar o navegador ficar entre elas.

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

EDIT: Acabei de perceber isso não vai funcionar. O valor de i será o mesmo para cada iteração de loop, você precisa fazer um fechamento.

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

Você precisa usar os Web Workers

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

Existem muitos links para funcionários da web se você pesquisar no google

Precisamos liberar o controle do navegador de vez em quando para evitar monopolizar a atenção do navegador.

Uma maneira de liberar o controle é usar um setTimeout , que programa um “callback” para ser chamado em algum período de tempo. Por exemplo:

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

Chamar f1 aqui adicionará a palavra hello ao seu documento, agendará uma computação pendente e liberará o controle para o navegador. Eventualmente, f2 será chamado.

Note que não é o suficiente para espalhar o setTimeout indiscriminadamente em todo o seu programa como se fosse um magic pixie dust: você realmente precisa encapsular o resto do cálculo no callback. Normalmente, o setTimeout será a última coisa em uma function, com o resto do cálculo recheado no retorno de chamada.

Para o seu caso particular, o código precisa ser transformado cuidadosamente para algo como isto:

 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); }; 

que irá liberar o controle para o navegador em todos os restOfComputation .

Como outro exemplo concreto disso, consulte: Como posso enfileirar uma série de clipes de som HTML5

Os programadores JavaScript avançados precisam saber como fazer essa transformação de programa, ou então eles atingem os problemas que você está encontrando. Você verá que, se usar essa técnica, precisará escrever seus programas em um estilo peculiar, em que cada function que pode liberar o controle considere uma function de retorno de chamada. O termo técnico para este estilo é "estilo de passagem de continuação" ou "estilo asynchronous".

Eu vejo duas maneiras:

a) Você tem permissão para usar o recurso Html5. Em seguida, você pode considerar usar um thread de trabalho.

b) Você dividiu essa tarefa e enfileirou uma mensagem que apenas faz uma chamada de uma vez e itera, desde que haja algo para fazer.

Houve uma pessoa que escreveu uma biblioteca de JavaScript de backgroundtask específica para fazer esse trabalho pesado .. você pode dar uma olhada nesta questão aqui:

Executar tarefa em segundo plano em JavaScript

Não usei isso para mim, apenas usei o uso de thread também mencionado.

 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); } }()) 

Você pode fazer muitas coisas:

  1. otimizar os loops – se o trabalho pesado tem algo a ver com o access DOM, veja esta resposta
    • se a function estiver trabalhando com algum tipo de dados brutos, use matrizes digitadas MSDN MDN
  2. o método com setTimeout () é chamado eteration . Muito útil.

  3. a function parece ser muito simples para linguagens de programação não-funcionais. JavaScript ganha vantagem de retornos de chamada SO pergunta .

  4. um novo recurso é o web workers MDN MSDN wikipedia .

  5. a última coisa (talvez) é combinar todos os methods – com a maneira tradicional, a function está usando apenas um thread. Se você pode usar os trabalhadores da web, você pode dividir o trabalho entre vários. Isso deve minimizar o tempo necessário para concluir a tarefa.