Olá pessoal, tudo bom?

Saindo um pouco do mundo back-end, hoje falarei sobre um recurso do HTML5 que nos fornece a capacidade de executar javaScript em segundo plano. Este é o Web Worker.

HTML5_logo

INTRODUÇÃO

Há uns dois anos atrás eu escutei um podcast onde um desenvolvedor que é referência técnica no Brasil dizia: "programador web que não souber javascritpt, css e html não terá espaço no mercado num futuro bem próximo". Acredito que já alcançamos o futuro que ele se referia.

[editado data="01/10/2014 11:52:00"]

Atualmente, muito javascript está sendo usado em aplicações web, tanto no lado do cliente como no servidor. É comum em alguns momentos encontrarmos problemas relacionados a maneira que nossos scripts são executados.

[/editado]

Javascript roda em uma única thread, realizando uma tarefa de cada vez. Ao executar um algoritmo que exige um  pouco mais de processo, a tela do navegador ficará "travada" até que essa tarefa seja finalizada. Quando fazemos chamadas ao servidor conseguimos resolver isso facilmente utilizando Ajax. Mas, como fazer para que tarefas longas sejam executadas sem que a página pare de responder para o cliente enquanto essa tarefa não for finalizada? A resposta para essa pergunta está no tema dessa publicação, Web Worker.

WEB WORKER

Web Worker ou workers é um recurso do HTML5 que nos permite executar javascript em segundo plano. Dessa forma, mesmo execuções de longa duração não irão fazer que o navegador pare de responder até o termino dessa execução. Todos os principais browsers em suas ultimas versões dão suporte ao web worker. Embora seja um recurso muito interessante devemos utilizá-lo com cautela, pois, o uso de workers nos custa uma considerável porção de desempenho e memória, não podemos nos esquecer de que essas tarefas estão sendo executadas na máquina cliente. Vejamos alguns exemplos: Utilizarei o mesmo algoritmo em todos os exemplos, o algoritmo é um dos "problemas matemáticos" conhecido como Problema de Collatz, onde, para definir uma sequência a partir de um número inteiro positivo, temos as seguintes regras:

Se n é par, o próximo valor é n/2

Se n é ímpar, o próximo valor é 3n + 1

Usando a regra acima e iniciando com o número 13, geramos a seguinte sequência:

13 -> 40 -> 20 -> 10 -> 5 -> 16 -> 8 -> 4 -> 2 -> 1

Podemos ver que esta sequência (iniciando em 13 e terminando em 1) contém 10 termos. Embora ainda não tenha sido comprovada, sabemos que com qualquer número que você começar, a sequência resultante chega no número 1 em algum momento. Pois bem, o snippet  abaixo armazena na variável resultado o menor número inicial entre 1 e 20 que gera a maior sequencia, veja:

[sourcecode language="javascript"]
var contadorGeral = 0, contadorAux = 1, aux, resultado = 1;
for (var i = 1; i <= 20; i++) {
aux = i;
do {
contadorAux += 1;
if (aux % 2 == 0) {
aux /= 2;
}
else {
aux = ((aux * 3) + 1) / 2;
contadorAux += 1;
}
} while (aux != 1);
if (contadorAux > contadorGeral) {
resultado = i;
contadorGeral = contadorAux;
contadorAux = 1;
}
}
[/sourcecode]

Se o script acima for executado com um range de números inicial e final muito alto (ex: 1 e 1 000 000) o navegador ficará sem resposta ("travado") durante alguns segundos até que a execução seja finalizada. Podemos resolver esse problema fazendo com que esse script seja executado em segundo plano através de um Worker. Para fazer isso primeiramente devemos criar um novo arquivo .js e colocar nosso algoritmo dentro desse arquivo. No meu caso eu criei o arquivo Collatz.js. Seguindo em frente, vamos voltar ao nosso arquivo .js original, que no meu caso se chama app.js. Mesmo que todos os browsers atuais deem suporte para Workers, vamos fazer uma validação para verificar, fazemos isso utilizando o trecho de código abaixo:

[sourcecode language="javascript"]
//app.js
if(typeof(Worker) !== undefined){
//com suporte
}
else{
//sem suporte
}
[/sourcecode]

Ao validar que o browser suporta o uso de Workers vamos criar um objeto Worker passando um argumento para seu construtor, esse argumento será o caminho do arquivo .js onde se encontra o script que será executado em segundo plano, nesse caso o arquivo Collatz.js, ficando da seguinte forma:

[sourcecode language="javascript"]
//app.js
if(typeof(Worker) !== undefined)
{
var w = new Worker('/Assets/js/Collatz.js');
}
else{
//sem suporte
}
[/sourcecode]

Repare que eu precisei navegar pela pasta Assets e js até chegar ao arquivo Collats.js. Bom, já temos quase tudo pronto,  agora veremos como executar o script que está no arquivo externo Collatz. O Objeto worker se “comunica” com o arquivo externo através de mensagens, para iniciar a execução do script que está no arquivo Collatz devemos enviar uma mensagem para ele, fazemos isso utilizando o método postMessage(), dessa forma:

[sourcecode language="javascript"]
//app.js
if(typeof(Worker) !== undefined){
var w = new Worker('/Assets/js/Collatz.js');
w.postMessage();
}
else{
//sem suporte
}
[/sourcecode]

No exemplo acima, w.postMessage() inicia o download do arquivo "/Assets/js/Collatz.js" que foi informado na criação do objeto Worker, o download é assíncrono e caso o arquivo não exista será retornado um erro 404 (not found). Ao finalizar o download do arquivo sua execução será iniciada. Beleza, executamos o arquivo e nada aconteceu. Na verdade aconteceu sim, nós só não estamos tratando isso. Da mesma forma que o postMessage() envia mensagem para o arquivo externo, esse arquivo também pode usar o método postMessage() para enviar mensagem para o objeto que o chamou, então vamos fazer isso e retornar o resultado do algorítimo para que quem o chamou possa tratá-lo. Veja abaixo o arquivo Collatz.js alterado para enviar o resultado para quem fez sua chamada:

[sourcecode language="javascript"]
//Collatz.js
var contadorGeral = 0, contadorAux = 1, aux, resultado = 1;
for (var i = 1; i <= 20; i++) {
aux = i;
do {
contadorAux += 1;
if (aux % 2 == 0) {
aux /= 2;
}
else {
aux = ((aux * 3) + 1) / 2;
contadorAux += 1;
}
} while (aux != 1);
if (contadorAux > contadorGeral) {
resultado = i;
contadorGeral = contadorAux;
contadorAux = 1;
}
}
//retornando o resultado
postMessage(resultado);
[/sourcecode]

Pronto, estamos enviando para o objeto Worker o resultado da execução do script Collats.js, mas, isso não é tudo que precisamos fazer para conseguir recuperar esse resultado, agora precisamos criar um listener no arquivo app.js para ficar “escutando” as mensagens que serão enviadas para o objeto Worker, veja a criação desse listener abaixo:

[sourcecode language="javascript"]
//app.js
if(typeof(Worker) !== undefined){
var w = new Worker('/Assets/js/Collatz.js');
w.postMessage(); //iniciando o worker
//listener
w.onmessage = function(retorno){
console.log(retorno.data); //tratando o retorno
}
}
else{
//sem suporte
}
[/sourcecode]

Agora tudo certo, criamos o listener onmessage para tratar o retorno do script Collatz.js. Existe outro listener que podemos usar, dessa vez para tratar erros, o onerror. Sua declaração é bem parecida com a do onmessage, veja:

[sourcecode language="javascript"]
//app.js
w.onerror = function(event) {
console.log('Erro no arquivo: ' + event.filename + 'nlinha: ' + event.lineno +  'nDescrição: ' + event.message);
};
[/sourcecode]

O tratamento de erro acima será executado quando um erro for disparado e nos mostrará em qual arquivo, número da linha que ocorreu e descrição do erro respectivamente, o ‘n’ foi usado para quebrar linha entre uma informação e outra.

Para finalizar, vamos deixar o código um pouco mais dinâmico, fazendo com que o script Collatz.js receba dois parâmetros, que serão o número inicial e final usados no calculo. Primeiro vamos alterar o arquivo Collatz.js para que ele passe a receber parâmetros. Como o Worker se comunica através de mensagens, você verá que o código usado é bem parecido com o código usado para tratar o retorno do script, vejamos:

[sourcecode language="javascript"]
/Collatz.js
//listener "escutando" mensagens
onmessage = function (param) {
var contadorGeral = 0, contadorAux = 1, aux, resultado = 1, numInicial = param.data[0], numFinal = param.data[1];
for (var i = 1; i <= 20; i++) {
aux = i;
do {
contadorAux += 1;
if (aux % 2 == 0) {
aux /= 2;
}
else {
aux = ((aux * 3) + 1) / 2;
contadorAux += 1;
}
} while (aux != 1);
if (contadorAux > contadorGeral) {
resultado = i;
contadorGeral = contadorAux;
contadorAux = 1;
}
}
postMessage(resultado);
};
[/sourcecode]

Basicamente o que fizemos foi criar um listener que ficará “escutando” as mensagens, que nesse caso será um parâmetro, que são enviados para ele. Repare que param é um Array, quando precisamos enviar mais de um valor para o Worker devemos fazer isso utilizando um Array ou um objeto json. Agora que o arquivo Colatz.js já está preparado para receber parâmetros, vamos fazer com que a chamada do Worker envie parâmetros, veja isso logo abaixo:

[sourcecode language="javascript"]
//app.js
if(typeof(Worker) !== undefined){
var w = new Worker('/Assets/js/Collatz.js');
//iniciando o worker
var parametro= new Array; //Criando objeto Array
parametro.push(1);
parametro.push(20);
w.postMessage(parametro);  //enviando parametro para o Worker
//listener
w.onmessage = function(retorno){
//tratando o retorno
console.log(retorno.data);
}
}
else{
//sem suporte
}
[/sourcecode]

Para enviar dois números como parâmetros, criamos o Array parâmetro e usando o método push(), que adiciona um elemento no final do Array, adicionamos o número 1 e o número 20 para que sejam enviados para o script Colatz.js.

Bom pessoal, era isso, fácil e com poucas linhas de código é possível implementar um Worker, para ler a documentação do web worker acesse o site da w3c em http://www.w3.org/TR/workers/.

Até o próximo post!