Como Implementar Web Workers em JavaScript
Introdução
JavaScript é uma linguagem de execução single-threaded, o que significa que apenas um script é interpretado e executado de cada vez. A execução é linear e sequencial, o que implica que eventos, manipuladores de eventos e callbacks não podem ser processados simultaneamente.
Por exemplo, ao enviar uma requisição Ajax para o servidor, o script que dispara a solicitação continua executando até que o servidor responda. Quando a resposta chega, o callback associado é executado, interrompendo temporariamente a execução do código ao redor. Esse modelo pode criar gargalos em operações mais intensivas, que impactam diretamente a responsividade do aplicativo.
A Web Worker API resolve essas limitações ao permitir a execução de tarefas em segundo plano. Web workers utilizam threads separadas para processar informações de maneira paralela, garantindo que o thread principal permaneça disponível para operações críticas, como a interação do usuário.
Criando um Web Worker
Para criar um Web Worker, utiliza-se o construtor Worker:
const worker = new Worker("worker.js");
O script que o worker executa deve estar em um arquivo separado. Esse caminho é passado como parâmetro para o construtor Worker
.
O Web Worker criado com a função Worker()
é chamado de dedicated web worker (worker dedicado).
ℹ️ Para que o Web Worker funcione corretamente, tanto a página web quanto os arquivos do Web Worker devem ser servidos por um servidor web. Neste exemplo, usaremos o Node.js como servidor por simplicidade, mas qualquer outra tecnologia de servidor pode ser utilizada.
Exemplo Prático
Criaremos uma pasta no disco com os seguintes arquivos:
index.html
: Página principal da aplicação.worker.js
: Script executado pelo Web Worker.server.js
: Código para o servidor Node.js.
Definindo a Página Web e Criando o Web Worker
No arquivo index.html
, adicione o seguinte código:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Web Worker Example</title>
</head>
<body>
<script>
const worker = new Worker("worker.js");
</script>
</body>
</html>
Aqui, apenas criamos uma instância do Web Worker que executará o código no arquivo worker.js
.
Definindo o Código do Web Worker
No arquivo worker.js
, adicione o seguinte código:
let result = 1;
const intervalID = setInterval(work, 1000);
function work() {
result = result * 2;
console.log("result =", result);
if (result >= 32) clearInterval(intervalID);
}
Este código usa a função setInterval()
para executar a função work
a cada segundo. A função multiplica a variável result
por 2, salva o novo valor e o exibe no console. Quando result
atinge 32, o timer é encerrado, finalizando o script do Web Worker.
Definindo o Servidor
Para servir a página corretamente, precisaremos de um servidor web. No arquivo server.js
, adicione o seguinte código para criar um servidor local com Node.js:
const http = require("http");
const fs = require("fs");
http
.createServer((request, response) => {
let filePath = request.url.substring(1);
if (!filePath) filePath = "index.html";
response.setHeader("Content-Type", "text/html; charset=utf-8;");
fs.readFile(filePath, (error, data) => {
if (error) {
response.statusCode = 404;
response.end("<h1>Resource not found!</h1>");
} else {
response.end(data);
}
});
})
.listen(3000, () => console.log("Servidor iniciado em http://localhost:3000"));
Vamos analisar o código. Primeiro, importamos os pacotes que oferecem as funcionalidades necessárias:
const http = require("http"); // para processar solicitações recebidas
const fs = require("fs"); // para ler arquivos do sistema de arquivos
Para criar o servidor, utilizamos a função http.createServer()
. Essa função recebe como argumento uma função de callback, que será executada toda vez que o servidor receber uma solicitação. A função callback possui dois parâmetros: request
(contém os dados da solicitação) e response
(gerencia o envio da resposta).
Na função de callback, podemos usar a propriedade request.url para obter o caminho do recurso solicitado. O objetivo é processar requisições para páginas como index.html
e home.html
, bem como para outras páginas HTML no futuro. Normalmente, os caminhos começam com uma barra /
. Por exemplo, uma solicitação para a página home.html
teria o caminho /home.html
. Para obter o caminho correto no sistema de arquivos, removemos a barra inicial com:
let filePath = request.url.substring(1);
Caso a solicitação seja direcionada para a raiz do site (apenas /
), o caminho resultante seria uma string vazia. Assim, assumimos que a página solicitada é a principal, index.html
:
if (!filePath) filePath = "index.html";
Como o servidor retornará HTML neste caso, utilizamos o método setHeader()
para configurar o cabeçalho da resposta com o tipo de conteúdo text/html
e a codificação UTF-8:
response.setHeader("Content-Type", "text/html; charset=utf-8;");
Em seguida, usamos a função fs.readFile
para ler o arquivo solicitado. O primeiro argumento é o caminho do arquivo (assumindo que ele está na mesma pasta que o arquivo server.js
). O segundo argumento é uma função callback que é chamada após a leitura do arquivo, recebendo um possível erro e o conteúdo do arquivo como parâmetros. Se o arquivo solicitado não for encontrado, retornamos uma resposta com código de status 404 e uma mensagem de erro:
fs.readFile(filePath, (error, data) => {
if (error) { // em caso de erro
response.statusCode = 404;
response.end("<h1>Resource not found!</h1>");
}
Caso não haja erro, o conteúdo do arquivo é enviado como resposta:
else {
response.end(data);
}
Por fim, iniciamos o servidor na porta 3000 utilizando a função listen()
. Com isso, o servidor estará disponível no endereço http://localhost:3000/
:
.listen(3000, () => console.log("Servidor iniciado em http://localhost:3000"));
Executando o Servidor
Agora, no terminal, navegamos até a pasta onde está o servidor utilizando o comando cd
e iniciamos o servidor com o comando node server.js
:
C:\app>node server.js Servidor iniciado em http://localhost:3000
Depois de iniciar o servidor, abrimos o navegador e acessamos o endereço http://localhost:3000
. A página exibida contém o código JavaScript que cria o Web Worker. Esse Web Worker executará a tarefa definida no arquivo worker.js. Os resultados do processamento aparecerão no console do navegador.
result = 2 result = 4 result = 8 result = 16 result = 32
Limitações dos Web Workers
No exemplo acima, o Web Worker utilizou um temporizador criado com a função setInterval()
. Contudo, nem todas as funcionalidades do JavaScript padrão do navegador estão disponíveis em tarefas realizadas por Web Workers. Em particular, Web Workers não têm acesso ao DOM nem ao objeto window
. No entanto, algumas propriedades e métodos do objeto window
são acessíveis, como a função setInterval(
) utilizada no exemplo.
A seguir, uma lista de funções que podem ser usadas em Web Workers:
atob()
btoa()
clearInterval()
clearTimeout()
queueMicrotask()
setInterval()
setTimeout()
structuredClone()
requestAnimationFrame()
(apenas para Web Workers dedicados)cancelAnimationFrame()
(apenas para Web Workers dedicados)
Além disso, os seguintes objetos e propriedades do objeto window
estão disponíveis:
console
location
navigator
indexedDB
Web Workers também podem acessar várias APIs do navegador, como:
- Barcode Detection API
- Broadcast Channel API
- Cache API
- Channel Messaging API
- Console API
- Web Crypto API (como Crypto)
- CSS Font Loading API
- CustomEvent
- Encoding API (como TextEncoder e TextDecoder)
- Fetch API
- File API
- FileReader
- FormData
- ImageBitmap
- ImageData
- IndexedDB
- Media Source Extensions API
- Network Information API
- Notifications API
- OffscreenCanvas e APIs relacionadas ao contexto de elementos
canvas
- Performance API
- Server-Sent Events
- ServiceWorkerRegistration
- URL API
- WebCodecs API
- WebSocket
- XMLHttpRequest
Utilizando self em Web Workers
Dentro do script de um Web Worker, podemos acessar o objeto do próprio Web Worker usando a palavra-chave self
. Por exemplo:
console.log(self); // exibe dados sobre o Web Worker
let result = 1;
const intervalID = setInterval(work, 1000);
function work() {
result = result * 2;
console.log("result =", result);
if (result >= 32) clearInterval(intervalID);
}
Nesse caso, self
pode ser usado para obter informações sobre o próprio Web Worker.
Interrompendo um Web Worker
Um Web Worker pode continuar funcionando indefinidamente enquanto o usuário permanece na página. Para finalizar a execução de um Web Worker, o método terminate()
é fornecido pela interface Worker
. Vamos modificar o código da página index.html
para incluir uma forma de interromper o Web Worker:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>www.programicio.com</title>
</head>
<body>
<button id="btn">Stop</button>
<script>
const worker = new Worker("worker.js");
// Ao clicar no botão, o Web Worker será interrompido
document.getElementById("btn").addEventListener("click", () => {
worker.terminate();
console.log("Web Worker interrompido");
});
</script>
</body>
</html>
Aqui, um botão é adicionado à página web. Ao clicar no botão, o Web Worker é interrompido com o método terminate()
, e uma mensagem indicando que o Web Worker foi parado é exibida no console do navegador.
Documentação oficial: