Entendendo Web Workers e como utilizá-los com React

Vinnicius Gomes
9 min readJan 9, 2024
Foto de Dominik Vanyi na Unsplash

Nos últimos anos, as aplicações web têm se tornado cada vez mais complexas e ricas em recursos, levando a uma maior demanda por processamento e gerenciamento de tarefas. Neste contexto, os Web Workers surgem como uma solução eficiente para melhorar o desempenho das aplicações. Neste artigo, vamos explorar o que são Web Workers, como funcionam e como integrá-los com o React.

O que são Web Workers?

Web Workers é uma API do navegador que permitem a execução de scripts em segundo plano em uma thread separada da thread principal da interface do usuário. Eles são usados para executar tarefas que exigem um esforço do processador, como cálculos matemáticos complexos ou processamento de imagens, sem interferir no desempenho da interface do usuário.

Os Web Workers funcionam criando um novo objeto de worker que carrega um arquivo JavaScript separado. O código dentro desse arquivo é executado em outra thread, sem acesso direto ao DOM, ao objeto window ou a outros recursos da interface do usuário. Em vez disso, os Web Workers se comunicam com a thread principal usando mensagens, permitindo que a comunicação aconteça sem causar bloqueios na interface do usuário.

Os Web Workers podem ser uma ferramenta poderosa para melhorar o desempenho e a escalabilidade de aplicativos web, especialmente aqueles que envolvem cálculos intensivos ou grandes volumes de processamento de dados.

Exemplo de Web Workers

1. Crie o arquivo do Web Worker:
Crie um arquivo JavaScript separado para o seu Web Worker, por exemplo, meuWorker.js. Dentro deste arquivo, você pode incluir qualquer código que deseja executar em background:

// meuWorker.js
onmessage = function (event) {
const data = event.data;
const resultado = calcularTarefaPesada(data);

postMessage(resultado);
};

function calcularTarefaPesada(data) {
// Código da tarefa pesada
}

2. Inserir o Web Worker na aplicação:
Para utilizar o Web Worker na sua aplicação, é necessário instanciá-lo no arquivo principal da sua aplicação. Utilize a classe Worker para criar um novo objeto Web Worker, passando como parâmetro o caminho do arquivo JavaScript do Web Worker:

// app.js
const worker = new Worker("meuWorker.js");

3. Comunicação entre a aplicação e o Web Worker:
Para enviar dados ao Web Worker, utilize o método postMessage. Para receber mensagens, adicione um evento de message à instância do Worker:

// app.js
worker.postMessage("Dado para processamento");

worker.onmessage = function (event) {
const resultado = event.data;
console.log("Resultado recebido do Web Worker:", resultado);
};

Funcionamento detalhado

1. Envio de dados (postMessage):

  • A thread principal chama postMessage no objeto do worker e envia dados para o worker. Neste caso, é uma string, mas poderia ser qualquer dado serializável.
  • O Web Worker processa esses dados em segundo plano, realizando as tarefas necessárias.
// Thread principal
const worker = new Worker('worker.js');
worker.postMessage("Dado para processamento");

2. Recepção de dados (onmessage):

  • No Web Worker, o evento onmessage é acionado sempre que a thread principal envia uma mensagem usando postMessage.
  • A função anônima atribuída a onmessage é executada, e o objeto event contém o dado enviado pela thread principal, acessível através de event.data.
  • O Web Worker processa o dado e, se necessário, envia de volta o resultado para a thread principal usando postMessage novamente.
// Web Worker
onmessage = function (event) {
const dadoRecebido = event.data;
// Processar o dado em segundo plano
const resultado = processarDado(dadoRecebido);
// Enviar o resultado de volta para a thread principal
postMessage(resultado);
};

function processarDado(dado) {
// Lógica de processamento
// ...
return resultado;
}

3. Recepção de resultado na thread principal:

  • Na thread principal, a função atribuída a onmessage é chamada quando o Web Worker envia de volta o resultado usando postMessage.
  • O objeto event contém o resultado, e a lógica na função anônima pode manipular ou exibir esse resultado conforme necessário.
// Thread principal
worker.onmessage = function (event) {
const resultado = event.data;
console.log("Resultado recebido do Web Worker:", resultado);
};

Essa abordagem de comunicação é assíncrona e permite que a thread principal e o Web Worker realizem tarefas em paralelo, contribuindo para uma melhor experiência do usuário em aplicações web intensivas em processamento.

Integrando Web Workers com React

Agora que você entendeu como funcionam os Web Workers, vamos integrá-los com uma aplicação React.

1. Criando o componente:

Crie um novo componente e utilize o useState para gerenciar o estado do resultado:

// MeuComponente.js
import React, { useState } from "react";

const MeuComponente = () => {
const [resultado, setResultado] = useState(null);

// Restante do código

return (
<div>
{/* Renderização do resultado */}
</div>
);
};

export default MeuComponente;

2. Instanciando o Web Worker com o useEffect:

Utilize o useEffect para criar uma instância do Web Worker e gerenciar seu ciclo de vida:

// MeuComponente.js
import React, { useState, useEffect } from "react";

const MeuComponente = () => {
const [resultado, setResultado] = useState(null);

const worker: Worker = useMemo(
() => new Worker(new URL("./meuWorker.ts", import.meta.url)),
[]
);

useEffect(() => {
worker.postMessage("Dado para processamento");
worker.onmessage = (event) => {
const resultado = event.data;
setResultado(resultado);
};

// Limpa e encerra o Web Worker quando o componente é desmontado
return () => {
worker.terminate();
};
}, []);

return (
<div>
{/* Renderização do resultado */}
<p>Resultado: {resultado}</p>
</div>
);
};

export default MeuComponente;

Neste exemplo, utilizamos o useState para gerenciar o estado do resultado e o useEffect para criar e gerenciar o ciclo de vida do Web Worker. O useEffect é executado quando o componente é montado, e a função de limpeza retornada dentro do useEffect é executada quando o componente é desmontado, garantindo que o Web Worker seja encerrado corretamente.

Exemplo prático: Calculadora de números primos

Criaremos um exemplo prático para calcular números primos com Web Workers e React.

1. Crie o arquivo do Web Worker:

Crie um arquivo separado para o seu Web Worker, por exemplo, primesWorker.js. Dentro deste arquivo, inclua o código para calcular números primos:

// primesWorker.js
onmessage = (event) => {
const { start, end } = event.data;

const primes = findPrimesInRange(start, end);

postMessage(primes);
};

function findPrimesInRange(start: number, end: number): number[] {
const primes: number[] = [];

for (let number = start; number <= end; number++) {
if (isPrime(number)) {
primes.push(number);
}
}

return primes;
}

function isPrime(num: number): boolean {
if (num < 2) return false;

for (let i = 2; i <= Math.sqrt(num); i++) {
if (num % i === 0) return false;
}

return true;
}

2. Integrando o Web Worker com o componente React:

No componente App.tsx e utilize o useState para gerenciar o estado dos números primos. Vamos criar também uma função para chamar nosso Worker:

// App.tsx
import { useState } from "react";
import "./App.css";

function App() {
// Estados para armazenar números primos, valores de entrada e estado de carregamento
const [primes, setPrimes] = useState<number[]>([]);
const [startNumber, setStartNumber] = useState(1);
const [endNumber, setEndNumber] = useState(1000);
const [loading, setLoading] = useState(false);

// Função para lidar com a busca de números primos
const handleFindPrimes = () => {
if (window.Worker) {
// Ativando o estado de carregamento
setLoading(true);

// Criando um novo Web Worker
const worker = new Worker(new URL("./primesWorker.ts", import.meta.url));

// Enviando uma mensagem para o worker com os números de início e fim
worker.postMessage({ start: startNumber, end: endNumber });

// Event listener para mensagens do worker
worker.onmessage = (event) => {
// Recebendo números primos do worker e atualizando o estado
const primesInRange = event.data;
setPrimes(primesInRange);

// Desativando o estado de carregamento e encerrando o worker
setLoading(false);
worker.terminate();
};
} else {
// Exibindo um erro se Web Workers não são suportados no ambiente
console.error("Web Workers não são suportados neste ambiente.");
}
};

return (
<div>
<h2>Calculadora de Números Primos com Web Worker</h2>

{/* Campos de entrada para números de início e fim */}
<div className="form-wrapper">
<input
type="number"
placeholder="Número inicial"
value={startNumber}
onChange={(e) => setStartNumber(Number(e.target.value))}
/>
<input
type="number"
placeholder="Número final"
value={endNumber}
onChange={(e) => setEndNumber(Number(e.target.value))}
/>
</div>

<br />

{/* Botão para iniciar a busca de números primos */}
<button onClick={handleFindPrimes} disabled={loading}>
{loading ? "Procurando primos..." : "Encontrar primos"}
</button>

{/* Exibição dos números primos encontrados ou mensagem de carregamento */}
<p>
<b>
Números primos no intervalo de {startNumber} a {endNumber}:
</b>
<br /> {loading ? <p>Carregando...</p> : primes.join(", ")}
</p>
</div>
);
}

export default App;

Com isso, você terá criado uma aplicação que calcula números primos utilizando Web Workers, sem bloquear a thread principal e garantindo uma experiência de usuário mais fluída e rápida.

Ao utilizar Web Workers junto com o React, é possível melhorar o desempenho das aplicações web, executando tarefas pesadas em background sem bloquear a interface do usuário.

Agora que você aprendeu como utilizar Web Workers com o React, você pode aplicar esse conhecimento para otimizar suas aplicações e garantir uma experiência de usuário ainda melhor.

Lembre-se de que os Web Workers são especialmente úteis para tarefas que exigem processamento intenso e podem ser executadas em paralelo à thread principal. Ao utilizar Web Workers de forma eficiente, você pode criar suas aplicações web com mais desempenho.

Prós e contras do uso de Web Workers:

Prós:

  1. Desempenho melhorado:
    Os Web Workers possibilitam a execução de operações intensivas em CPU em segundo plano, sem interromper a thread principal. Isso melhora significativamente o desempenho e a responsividade das aplicações.
  2. Multithreading simulado:
    Os Web Workers permitem a execução de código em paralelo, simulação de multithreading no JavaScript. Isso é valioso para processamento de dados pesados, como cálculos matemáticos complexos.
  3. Melhoria na responsividade da interface do usuário:
    Ao mover operações demoradas para um Web Worker, a interface do usuário permanece responsiva, pois as operações em segundo plano não bloqueiam a execução do código na thread principal.
  4. Isolamento de contexto:
    Cada Web Worker é executado em seu próprio contexto, isolando-o do contexto principal. Isso evita conflitos de variáveis globais e promove uma abordagem mais segura e modular.
  5. Melhor utilização de hardware multi-core:
    Aplicações podem tirar proveito de hardware multi-core de maneira eficiente, pois cada Web Worker é executado em seu próprio thread, permitindo a utilização máxima de recursos disponíveis.

Contras:

  1. Compartilhamento limitado de dados:
    A comunicação entre a thread principal e os Web Workers envolve a serialização e desserialização de dados, o que pode ser custoso. O compartilhamento direto de objetos entre threads não é possível, exigindo a passagem de mensagens.
  2. Complexidade adicional:
    O uso de Web Workers pode introduzir complexidade adicional ao código, especialmente para operações que requerem comunicação frequente entre a thread principal e os Web Workers.
  3. Limitações de acesso ao DOM:
    Web Workers não têm acesso direto ao DOM, o que pode complicar operações que dependem fortemente da manipulação do DOM. A comunicação com a thread principal é necessária para realizar atualizações na interface do usuário.
  4. Suporte variável:
    Embora a maioria dos navegadores modernos ofereça suporte a Web Workers, é importante verificar a compatibilidade em navegadores específicos, especialmente se o público-alvo incluir versões mais antigas.
    Você pode conferir a lista de navegadores que suportam aqui: https://caniuse.com/webworkers

Algumas sugestões de quando utilizar Web Workers:

  1. Cálculos intensivos:
    Utilize Web Workers para realizar cálculos matemáticos complexos, processamento de imagem ou outras operações intensivas em CPU sem prejudicar a responsividade da interface do usuário.
  2. Operações de longa duração:
    Para operações que podem levar tempo significativo para serem concluídas, como processamento de grandes conjuntos de dados, use Web Workers para evitar bloqueios na thread principal.
  3. Melhorar a experiência do usuário:
    Ao realizar tarefas em segundo plano, como pré-carregamento de dados ou processamento offline, Web Workers podem melhorar a experiência do usuário, mantendo a aplicação ágil e responsiva.
  4. Aproveitar recursos de hardware:
    Quando há a necessidade de utilizar eficientemente os recursos de hardware multi-core, Web Workers oferecem uma solução para paralelizar tarefas e otimizar o desempenho.

Ao implementar Web Workers em suas aplicações, é importante avaliar os requisitos específicos do projeto e considerar os prós e contras para determinar se essa abordagem é a mais adequada para atender aos objetivos de desempenho e experiência do usuário.

E é dessa forma que podemos realizar tarefas complexas no Frontend sem prejudicar o desempenho 🚀

Ah e eu criei um repositório no GitHub com o código utilizado nesse post, se você quiser dar uma olhada mais a fundo é só clicar nesse link:

Bom, é isso, espero que tenha gostado! E se tiver alguma sugestão deixe aí nos comentários 💬

Se gostou, dê 1 ou 50 claps 👏

Obrigado pela leitura!

Me acompanhe por aí! 😜

--

--

Vinnicius Gomes

Senior Software Engineer who love to write about Frontend, JavaScript and Web development. See more about me — vinniciusgomes.dev