Tableless

Busca Menu

Mais Válvulas para seu Javascript

Seja o primeiro a comentar por

Enquanto você pensa sobre como resolver um bug em seu código e ao mesmo tempo balança a cadeira e inconscientemente levanta o braço para tomar mais uma xícara de café, seu cérebro está realizando algo que talvez possamos chamar de multithreading. Esses pequenos atos do dia-a-dia parecem indicar que estar consciente não atrapalha a sua interface (corpo) de agir da maneira correta.

Quem não sente fascinio por um v8? Que tal se suas aplicações e paginás pudessem experimentar essa potência?

Quem não sente fascinio por um v8? Que tal se suas aplicações e paginás pudessem experimentar essa potência?

Navegando na Web você poderá deparar-se com muitas situações nas quais o multithreading seria útil. Essas situações são notadas quando o ponteiro do mouse move-se lentamente, ou quando o seu navegador dispara um alerta.

web-pages-are-not-responding

 

Um dos motivos que levam a esse mal desempenho das páginas web pode estar relacionado com o Javascript. Pois como é sabido, o javascript é sequencial. Portanto, você deverá tomar cuidado para não consumir em excesso os recursos da máquina que estará acessando sua aplicação. Pois, todo o código javascript ocorrerá em uma única via. Quando existem carros demais em uma rodovia, obviamente ocorrerá um engarrafamento.
Um exemplo de bloqueio de interface pode ser testado no fiddle abaixo.

https://jsfiddle.net/messiasphysics/db11rafk/

No fiddle acima é possível testar um sort de 50k. Você poderá experimentar uma lentidão do seu computador, se você testar em um celular o resultado poderá ser ainda mais desagradável.
Felizmente o HTML5 trouxe os Web Workes. Os quais permitem o multithreading em páginas web, fornecendo mais uma alternativa para fugir do engarrafamento no js, e consequentemente permitindo a construção de aplicações web cada vez mais robustas.

Web Workers: Conceitos e aplicações

Web Workers são mecanismos que permitem que uma dada operação de um dado script seja executada em um thread diferente do thread principal da aplicação Web(adotaremos que o js responsável pela interação do usuário estará em index.js). Assim, cálculos laboriosos podem ser processados sem que ocorra bloqueio do thread principal(geralmente associado com a interface).
Os threads dos Workers e o thread principal comunicam-se entre-si via sistema de mensagens, você pode pensar que o thread principal delega problemas para os workers resolverem, e esses por sua vez enviam a resposta para o thread principal. Dessa maneira o thread principal pode ficar focado em fornecer uma interface navegável para o usuário.

O que um Web Worker não pode fazer?

É importante que você tenha em mente que um Worker não consegue manipular o DOM da página. Isso entra em acordo com o que eu tinha dito anteriormente, pois quem têm que cuidar da interface é o thread principal, workers são funcionários de segundo plano.

Como criar um Worker?

Antes de tentar criar um Worker seria interessante testar o suporte do navegador a esse recurso, isso pode ser feito facilmente com um

if (window.Worker) {
//Navegador compativel
....
}

Partindo da condição que o navegador  suporta tal recurso, escrevemos os trechos de código do Worker  dentro de um arquivo separado(mostrarei como podemos “burlar” isso). Por exemplo, criaremos um worker que  realize a soma de dois números. Portanto crie uma arquivo soma.js no mesmo diretório de index.js.

Com o soma.js criado(mas ainda em branco),  criamos o worker dentro do thread principal  inserindo a seguinte linha no index.js

var worker = new Worker('soma.js');

*é importante ressaltar que não é necessário carregar o arquivo soma.js na sua página através de uma tag script.

 

Quando o worker é criado o script soma.js será executado. Se você abrir o inspetor de elementos notará algo desse tipo

Comunicação

Agora que o worker foi criado é necessário estabelecer um canal de comunicação entre os dois threads. Permitindo que o thread  Main realize uma pregunta  ao thread do worker, e que esse por sua vez envie a resposta.

O thread Main envia a pergunta(mensagem) para o thread do worker  através do método postMessage().

var mensagem={
       tipo:'some',
       dados:{
            a:1,
            b:2
       }
};
worker.postMessage(mensagem);

Agora o thread do worker precisa escutar as perguntas. Para que ele consiga fazer isso é necessário criar  um listener para eventos do tipo message dentro de soma.js

self.addEventListener('message',function(mensagem){
    if(mensagem.data.tipo=='some'){
       tarefaSomar(mensagem.data.dados);
    }
});
function tarefaSomar(numeros){
    var resultado =(numeros.a+numeros.b).toString();
}

Nosso worker agora está escutando o thread Main, e executando uma dada tarefa de acordo com o tipo da mensagem enviada, mas nosso worker não consegue estabelecer um diálogo. Pois, para estabelecer tal diálogo  é necessário que ele envie a resposta para a pergunta feita pelo  thread Main. Dessa maneira, é necessário criar um método postMessage dentro de soma.js

function tarefaSomar(numeros){
    var resultado = (numeros.a+numeros.b).toString();
    self.postMessage({tipo:'respostaSoma',resultado:resultado});
}

que enviará a resposta para o thread Main. E da mesma maneira como fizemos com o soma.js  é necessário que alguém dentro de index.js escute as respostas enviadas pelo worker. Portanto, dentro de index.js criamos   um listener

worker.addEventListener('message',function(mensagem){
    if(mensagem.data.tipo=='respostaSoma'){
       alert('O resultado da soma é '+mensagem.data.resultado);
    }
});

Com o listener acima o thread  Main obterá a resposta para a pergunta que ele fez.  Nesse ponto é interessante que você compreenda que os dados trafegados entre os threads -aqueles que são enviados via postMessage()-  não são compartilhados e sim copiados, ou seja, cada thread armazena a mensagem em um local distinto.

Agora que o thread  Main obteve a resposta para a pergunta desejada(supondo que ele não precise mais realizar tal pergunta)  podemos matar o worker! Dentro de index.js isso pode ser feito com

worker.terminate();

dentro de soma.js podemos fazer um

worker.close();

Tudo muito simples, não? Com os web workers você pode realizar tarefas dispendiosas sem alterar a fluidez da sua aplicação, mais um motivo para manter o código bem organizado!

No inicio do post  você testou um  sort de 50k e pode ter experimentado uma lentidão, é fácil aplicar os conhecimentos abordados nesse artigo para realizar tal sort dentro de um worker. Esse exemplo de utilização de um worker para realizar um sort  foi criado por Afshin Mehrabani, realizei algumas alterações  e coloquei mais algumas coisinhas no projeto de Afshin, e disponibilizei o resultado na seguinte página
http://ganexa.net/webworker

Se você quiser dar uma olhada no código basta acessar

https://github.com/ganexasi/webworker

 

Considerações finais

  • Esse artigo foi escrito para um leitor que não  leu nada sobre Web Workers
  • Nesse artigo discorri sobre um tipo de Web Worker denominado dedicado. Existem outros tipos de workers os quais gostaria de abordar em futuros posts.
  • É importante notar que usar um Promise(recomendo o excelente artigo do Jean sobre Promises) não terá o mesmo custo ou efeito que a utilização de um Web Worker, e um não substitui o outro.

Extra: Objeto Blob

Talvez você  queira executar uma tarefa dispendiosa  que pode ser feita por um código muito simples. Não seria meio chato criar  um arquivo separado para executar essa tarefa? O que pode ser feito  é criar uma string contendo o código, e transformar tal string em um “arquivo”. Confuso? Mas essa transformação é possível através do construtor Blob().

Um objeto Blob  talvez possa ser  chamado de objeto tipo arquivo(me corrijam se eu estiver errado),  de modo que dada uma string armazenada em um array, tal como abaixo

var workerString =[ 'self.addEventListener("message",function(mensagemRecebida){ self.postMessage("Worker says: Ola");},false)']

podemos criar um objeto Blob

var workerBlob= new Blob(workString,{type:'text/javascript'});

o que nos permite criar uma url para tal Blob

var workerUrl= URL.createObjectURL(workerBlob);

de posse de um endereço para nosso “arquivo” nos é permitido gerar um worker

var worker= new Worker(workerUrl);

Juntando tudo, e acrescentando o listener abordado na secção anterior ficamos com o fiddle abaixo

https://jsfiddle.net/messiasphysics/6267g5p0/

Você também poderia inserir o código doworker dentro de umatag//

https://jsfiddle.net/messiasphysics/wwppsLdh/

Interessante, não? Se você quiser mais informações a respeito do Blob basta acessar a especificação do html5 http://www.w3.org/TR/file-upload/#dfn-Blob.

Por hoje é só, obrigado pela atenção.

Publicado no dia