Definindo e Implementando Web Workers

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).

Nota: 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.

Política de Privacidade

Copyright © www.programicio.com Todos os direitos reservados

É proibida a reprodução do conteúdo desta página sem autorização prévia do autor.

Contato: programicio@gmail.com