HTML5 Web Workers: Multithreading Prático | Web Academy
Tradicionalmente, os navegadores web executam o código JavaScript em uma thread única (a chamada Main Thread ou Thread de UI). Nessa arquitetura, o mesmo motor encarregado de processar seus scripts também é responsável por redesenhar a tela, renderizar estilos CSS e responder a cliques e eventos de rolagem do usuário. Se você disparar uma operação pesada nessa thread, a página inteira simplesmente congela. A introdução dos HTML5 Web Workers quebrou essa barreira, trazendo o verdadeiro multithreading para a web e permitindo rodar cálculos intensivos em segundo plano com fluidez total.
📑 Índice do Conteúdo
1. O Problema do Congelamento da Interface (Jank)
Imagine que sua aplicação precise ordenar uma matriz com 500 mil registros, calcular filtros matemáticos sobre uma foto em alta definição ou processar um arquivo volumoso.
Se esse processamento for executado na thread principal:
- As animações CSS e transições de tela caem de 60 FPS para zero.
- O cursor do mouse para de responder e os botões parecem quebrados.
- Após alguns segundos, o navegador exibe a temida janela de alerta: “Esta página não está respondendo. Deseja aguardar ou fechar?”.
💡 A Solução com Web Workers
Um Web Worker é uma linha de execução de segundo plano independente gerenciada pelo próprio sistema operacional. Ele opera em paralelo à interface visual: enquanto o Worker consome 100% de um núcleo de CPU triturando dados complexos, a página principal continua rolando suavemente a 60 quadros por segundo sem qualquer engasgo.
2. O Que um Web Worker Pode e NÃO Pode Acessar
Para evitar problemas clássicos de concorrência e corrupção de memória (como race conditions ou deadlocks), os Web Workers operam sob um modelo de isolamento estrito:
| Recurso | Acesso Permitido no Worker? | Motivo Técnico |
|---|---|---|
Árvore do DOM (window, document) |
❌ Proibido | O DOM não é seguro para operações concorrentes simultâneas de múltiplas threads. |
Rede e Comunicação (fetch, XMLHttpRequest, WebSocket) |
✅ Permitido | Permite baixar e processar grandes pacotes de dados diretamente em background. |
Temporizadores (setTimeout, setInterval) |
✅ Permitido | Controle temporal padrão do ambiente JavaScript. |
Bancos de Dados Locais (IndexedDB) |
✅ Permitido | Permite ler e gravar gigabytes de dados no cliente sem bloquear a tela. |
Importação de Módulos (importScripts()) |
✅ Permitido | Permite importar bibliotecas externas para o contexto do worker. |
3. O Mecanismo de Troca de Mensagens (postMessage)
Como a página principal e o Worker habitam universos isolados, toda a comunicação é feita através de passagem de mensagens serializadas:
// === NA PÁGINA PRINCIPAL (main.js) ===
// 1. Cria a thread dedicada apontando para o arquivo do worker
const meuWorker = new Worker('calculadora-worker.js');
// 2. Envia uma carga de dados para o worker processar
meuWorker.postMessage({ numeroInicial: 5000000 });
// 3. Ouve a resposta enviada de volta pelo worker
meuWorker.onmessage = function(evento) {
console.log('Resultado recebido do Worker:', evento.data.resultado);
};
// 4. Captura eventuais erros de execução
meuWorker.onerror = function(erro) {
console.error('Erro no Worker:', erro.message);
};
// 5. Encerra a thread quando não for mais necessária
// meuWorker.terminate();
// === DENTRO DO WORKER (calculadora-worker.js) ===
// O escopo global do worker é acessado via 'self'
self.onmessage = function(evento) {
const limite = evento.data.numeroInicial;
let totalPrimos = 0;
// Cálculo matemático intensivo que demoraria segundos
for (let i = 2; i <= limite; i++) {
let primo = true;
for (let j = 2; j * j <= i; j++) {
if (i % j === 0) {
primo = false;
break;
}
}
if (primo) totalPrimos++;
}
// Devolve o resultado para a thread principal
self.postMessage({ resultado: totalPrimos });
};
4. Código Prático Completo: Demonstração Interativa Sem Travamento
Para facilitar testes imediatos sem a necessidade de criar múltiplos arquivos no servidor, podemos gerar o Worker dinamicamente em memória através de um Blob URL:
<div class="wzw-worker-demo">
<h3>⚡ Demonstração de Concorrência Real com Web Worker</h3>
<!-- Elemento com animação CSS contínua para provar a fluidez visual -->
<div class="spinner-teste"></div>
<p><small>Observe como este ícone gira sem qualquer interrupção durante o cálculo pesado.</small></p>
<button id="btnCalcular" class="btn-worker">Calcular Números Primos em Background</button>
<div id="painelStatus" class="status-box">Aguardando comando...</div>
</div>
/* Animação que provará a ausência de congelamento da UI */
@keyframes girarSuave {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.spinner-teste {
width: 44px;
height: 44px;
margin: 12px 0;
border: 5px solid #334155;
border-top-color: #38bdf8;
border-radius: 50%;
animation: girarSuave 1s linear infinite;
}
.wzw-worker-demo {
padding: 24px;
background-color: #0f172a;
border: 1px solid #1e293b;
border-radius: 8px;
color: #f8fafc;
}
.btn-worker {
padding: 12px 20px;
background-color: #10b981;
color: #fff;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: 600;
}
.status-box {
margin-top: 15px;
padding: 12px;
background-color: #1e293b;
border-radius: 6px;
font-family: monospace;
}
// Código do Worker encapsulado em string para teste imediato
const codigoWorker = `
self.onmessage = function(e) {
const n = e.data;
let contagem = 0;
// Laço pesado proposital
for (let i = 0; i < n; i++) {
contagem += Math.sqrt(i);
}
self.postMessage('Cálculo concluído com sucesso: ' + Math.round(contagem));
};
`;
// Criação do Worker inline via Blob
const blob = new Blob([codigoWorker], { type: 'application/javascript' });
const workerUrl = URL.createObjectURL(blob);
const workerInline = new Worker(workerUrl);
const btn = document.getElementById('btnCalcular');
const status = document.getElementById('painelStatus');
btn.addEventListener('click', () => {
status.textContent = '⏳ Executando cálculo massivo em thread separada... A animação continua a 60 FPS!';
btn.disabled = true;
// Dispara a carga de trabalho para a thread paralela
workerInline.postMessage(20000000);
});
workerInline.onmessage = function(evento) {
status.textContent = '✅ ' + evento.data;
btn.disabled = false;
};
5. Transferência de Dados com Zero Latência (Transferable Objects)
Por padrão, o método postMessage() realiza uma cópia estruturada (structured clone) dos objetos. Para matrizes gigantes ou buffers de imagem com dezenas de megabytes gerados via Canvas, duplicar a memória pode causar lentidão momentânea.
Para resolver isso, o HTML5 suporta Transferable Objects (como instâncias de ArrayBuffer), onde a posse da memória é transferida instantaneamente para o Worker sem cópia física:
// Cria um buffer de 32 Megabytes
const bufferGigante = new ArrayBuffer(32 * 1024 * 1024);
// O segundo argumento transfere a posse com latência zero (Zero-Copy)
meuWorker.postMessage(bufferGigante, [bufferGigante]);
// Na thread principal, bufferGigante.byteLength agora vale 0!
6. Ferramentas Relacionadas & Próximos Passos
Continue explorando as tecnologias avançadas de processamento e dados da nossa suíte:
- Central do Desenvolvedor Web: Utilitários para medição de desempenho, validação e manipulação de scripts.
- Hub Master de Imagens: Filtros e conversores gráficos que utilizam processamento em segundo plano.
- Lição 34: Canvas: Gráficos 2D no Navegador: Combine renderização vetorial e rasterizada com Web Workers via OffscreenCanvas.
- Lição 40: Server-Sent Events (SSE): Receba transmissões contínuas de dados do servidor em tempo real.
7. Referências Técnicas & Especificações Oficiais
- WHATWG HTML Living Standard: Web Workers Specification
- MDN Web Docs: Web Workers API Architecture & Guides
- W3C Web Workers Working Draft