HTML - API de Web Workers

-

Conceitos Básicos de Web Worker

Os conceitos básicos de Web Worker abrangem a criação, comunicação e encerramento de Web Workers. Para criar um Web Worker, você especifica o arquivo de script que contém o código do worker. A comunicação entre o script principal e o Web Worker é feita por meio de troca de mensagens, usando o método postMessage() para enviar mensagens e o manipulador de eventos onmessage para receber mensagens. Quando um Web Worker não é mais necessário, você pode encerrá-lo usando o método terminate().

Tipos de Web Workers

Tipo Descrição
Workers Dedicados Workers Dedicados são o tipo mais comum de Web Worker. Eles são associados a um único script e podem se comunicar apenas com esse script. Workers Dedicados são criados usando o construtor Worker e são úteis para transferir tarefas que consomem muita CPU específicas de um script em particular.
Workers Compartilhados Workers Compartilhados permitem que vários scripts, mesmo de janelas ou iframes diferentes, se comuniquem com um único worker. Eles são criados usando o construtor SharedWorker. Workers Compartilhados são úteis para tarefas que precisam ser compartilhadas entre vários scripts ou janelas, como gerenciar um cache compartilhado ou fornecer um canal de comunicação centralizado.
Service Workers Service Workers são um tipo especial de Web Worker que atuam como um intermediário entre aplicações web, o navegador e a rede. Eles são projetados para interceptar solicitações de rede, armazenar recursos em cache e fornecer funcionalidade offline. Service Workers têm um ciclo de vida diferente e são criados usando o método navigator.serviceWorker.register(). Eles são comumente usados para construir Aplicativos Web Progressivos (PWAs) e melhorar o desempenho de aplicações web.

Cada tipo de Web Worker tem seu próprio caso de uso específico e modelo de comunicação. Workers Dedicados são o tipo mais simples, enquanto Workers Compartilhados e Service Workers oferecem funcionalidades mais avançadas para cenários específicos. Entender as diferenças entre esses tipos de Web Workers é importante para escolher o mais adequado às suas necessidades e implementá-los corretamente em sua aplicação web.

Uso de Web Workers

Web Workers fornecem um mecanismo para transferir tarefas intensivas de CPU, realizar tarefas em segundo plano e lidar com scripts de longa duração sem bloquear a thread principal de execução. Ao usar Web Workers, você pode melhorar o desempenho e a capacidade de resposta de suas aplicações web.

Um caso de uso para Web Workers é a transferência de tarefas intensivas de CPU. Quando você tem operações computacionalmente pesadas, como algoritmos complexos, processamento de imagens ou análise de dados, você pode mover essas tarefas para um Web Worker. Isso permite que a thread principal permaneça responsiva, pois os cálculos intensivos são realizados em uma thread separada. Os usuários podem continuar interagindo com a aplicação enquanto o Web Worker lida com o trabalho intensivo de CPU em segundo plano.

Web Workers também são úteis para realizar tarefas em segundo plano que não requerem interação imediata do usuário.

Exemplo: Pré-carregamento de dados com Web Workers

// Thread principal
const worker = new Worker('worker.js');
worker.onmessage = function(event) {
    console.log('Dados carregados:', event.data);
};
worker.postMessage('start');
// worker.js
self.onmessage = function(event) {
    if (event.data === 'start') {
        // Pré-carregar dados ou realizar tarefa em segundo plano
        self.postMessage('dados carregados');
    }
};

Ao executar essas tarefas em um Web Worker, você pode garantir que elas não interfiram na capacidade de resposta da thread principal. O Web Worker pode trabalhar em segundo plano, buscando dados ou realizando atualizações, sem impactar a experiência do usuário.

Outro cenário em que os Web Workers se destacam é no tratamento de scripts de longa duração.

Exemplo: Lidando com scripts de longa duração usando Web Workers

// Thread principal
const worker = new Worker('longTaskWorker.js');
worker.onmessage = function(event) {
    console.log('Tarefa concluída:', event.data);
};
worker.postMessage('start');
// longTaskWorker.js
self.onmessage = function(event) {
    if (event.data === 'start') {
        // Realizar tarefa de longa duração
        let result = realizarCalculoComplexo();
        self.postMessage(result);
    }
};

function realizarCalculoComplexo() {
    // Lógica de cálculo complexo
    return 'resultado calculado';
}

Se você tem um script que leva um tempo considerável para ser executado, como uma tarefa complexa de processamento de dados ou uma animação de longa duração, executá-lo na thread principal pode congelar a interface do usuário. Ao mover o script de longa duração para um Web Worker, você pode manter a thread principal livre para responder às interações do usuário. O Web Worker pode executar o script de forma assíncrona, permitindo que a aplicação permaneça responsiva.

Limitações dos Web Workers

Embora os Web Workers ofereçam benefícios, eles também têm limitações. É importante estar ciente dessas limitações ao decidir se os Web Workers são adequados para sua aplicação.

Limitação Descrição
Sem acesso ao DOM Web Workers não têm acesso ao Document Object Model (DOM). Um Web Worker não pode manipular ou atualizar diretamente os elementos da página web. Se um Web Worker precisar atualizar a interface do usuário, ele deve enviar uma mensagem de volta para a thread principal, que pode então atualizar o DOM.
Sem acesso ao objeto Window Web Workers não têm acesso ao objeto window. O objeto window fornece acesso a recursos e APIs específicos do navegador, como alert(), prompt() ou location. Web Workers são executados em um escopo global separado e não podem interagir diretamente com o objeto window.
Recursos JavaScript limitados Web Workers têm acesso limitado a certos recursos JavaScript. Por exemplo, Web Workers não podem acessar o objeto document, o objeto parent ou o objeto console. Eles também têm acesso restrito a algumas APIs do navegador, como o objeto XMLHttpRequest ou o objeto localStorage.

Apesar dessas limitações, os Web Workers ainda fornecem uma ferramenta para otimizar o desempenho de aplicações web. Ao projetar cuidadosamente a arquitetura da sua aplicação e o modelo de comunicação, você pode aproveitar os Web Workers para transferir tarefas intensivas, realizar trabalhos em segundo plano e lidar com scripts de longa duração.

Comunicação de Web Worker

A Comunicação de Web Worker envolve o envio de mensagens entre o script principal e o Web Worker, e o recebimento de mensagens do Web Worker de volta ao script principal. A comunicação é feita através do método postMessage() e do manipulador de eventos onmessage.

Para enviar uma mensagem a um Web Worker, você usa o método postMessage() do script principal. O método postMessage() recebe a mensagem como seu argumento, que pode ser um valor simples ou um objeto.

Exemplo: Enviando uma mensagem para um Web Worker

// Script principal
const worker = new Worker('worker.js');
worker.postMessage('Olá do script principal');

No lado do Web Worker, você pode receber a mensagem configurando um manipulador de eventos onmessage. O objeto de evento passado ao manipulador contém os dados da mensagem em sua propriedade data.

Exemplo: Recebendo uma mensagem no Web Worker

// worker.js
self.onmessage = function(event) {
    console.log('Mensagem recebida:', event.data);
};

Da mesma forma, o Web Worker pode enviar mensagens de volta ao script principal usando o método postMessage(). O script principal pode receber essas mensagens configurando seu próprio manipulador de eventos onmessage.

Exemplo: Enviando mensagens do Web Worker e recebendo-as no script principal

// worker.js
self.postMessage('Olá do Web Worker');

// Script principal
worker.onmessage = function(event) {
    console.log('Mensagem recebida do worker:', event.data);
};

Além de enviar valores simples ou objetos, você também pode transferir dados usando objetos transferíveis. Objetos transferíveis permitem que você passe grandes quantidades de dados, como ArrayBuffer ou MessagePort, entre o script principal e o Web Worker sem copiar os dados. Isso pode ajudar a melhorar o desempenho ao lidar com conjuntos de dados grandes. Para transferir dados, você passa um array de objetos transferíveis como segundo argumento para o método postMessage().

Exemplo: Transferindo dados grandes com objetos transferíveis

// Script principal
const dadosGrandes = new ArrayBuffer(1024 * 1024); // 1MB de dados
worker.postMessage({ data: dadosGrandes }, [dadosGrandes]);

Evento de Mensagem

O evento message é o evento chave para lidar com a comunicação entre o script principal e o Web Worker. Entender como o evento message funciona é importante para enviar e receber mensagens corretamente.

Quando uma mensagem é enviada usando o método postMessage(), um evento message é acionado no lado receptor. O objeto de evento contém as seguintes propriedades:

Propriedade Descrição
data Os dados da mensagem enviados pelo remetente.
origin A origem da janela do remetente (script principal).
source Uma referência ao objeto Worker do remetente (script principal) ou MessagePort (Web Worker).

No script principal, você pode lidar com mensagens do Web Worker configurando um manipulador de eventos onmessage no objeto Worker. O objeto de evento passado ao manipulador conterá os dados da mensagem em sua propriedade data.

Exemplo: Lidando com mensagens do Web Worker no script principal

// Script principal
worker.onmessage = function(event) {
    console.log('Mensagem recebida do worker:', event.data);
    // Lidar com a mensagem e executar ações
};

Da mesma forma, no script do Web Worker, você pode lidar com mensagens do script principal configurando um manipulador de eventos onmessage no objeto global self. O objeto de evento passado ao manipulador conterá os dados da mensagem em sua propriedade data.

Exemplo: Lidando com mensagens do script principal no Web Worker

// worker.js
self.onmessage = function(event) {
    console.log('Mensagem recebida:', event.data);
    // Lidar com a mensagem e executar ações
    self.postMessage('Mensagem processada');
};

Tratamento de Erros em Web Workers

O tratamento de erros é uma parte importante do trabalho com Web Workers. Envolve lidar com erros que podem ocorrer no script do Web Worker e enviar esses erros para o script principal para ação.

Para tratar erros em Web Workers, você precisa ouvir eventos de erro. O script do Web Worker pode acionar um evento de erro usando o manipulador de eventos self.onerror. Esse manipulador de eventos é chamado quando ocorre um erro no script do Web Worker.

Exemplo: Tratamento de erros no Web Worker

// worker.js
self.onerror = function(event) {
    console.error('Erro no Web Worker:', event.message);
    // Trate o erro ou faça tarefas de limpeza
    self.postMessage({ error: event.message });
};

No exemplo acima, o manipulador de eventos self.onerror é usado para capturar quaisquer erros que ocorram no script do Web Worker. Você pode registrar a mensagem de erro, tratar o erro ou realizar quaisquer tarefas de limpeza necessárias.

Para enviar o erro ao script principal, você pode enviar uma mensagem de erro usando self.postMessage(). O script principal pode então ouvir essa mensagem de erro e tomar uma ação.

Exemplo: Tratamento de erros no script principal

// Script principal
worker.onmessage = function(event) {
    if (event.data.error) {
        console.error('Erro recebido do Web Worker:', event.data.error);
        // Trate o erro no script principal
    }
};

No script principal, você pode verificar se a mensagem recebida tem uma propriedade de erro. Se um erro for recebido, você pode registrar a mensagem de erro e tratá-la.

Erros Comuns em Web Workers

Existem alguns erros comuns que você pode encontrar ao trabalhar com Web Workers:

Erro Descrição
Script não encontrado Esse erro ocorre quando o arquivo de script do Web Worker não pode ser encontrado ou carregado. Certifique-se de que o caminho do arquivo de script está correto e pode ser acessado.
Script inválido Esse erro ocorre quando o script do Web Worker tem sintaxe JavaScript inválida ou não é um arquivo de script válido. Verifique o script em busca de erros de sintaxe.
Limite de memória excedido Os Web Workers têm um limite de memória definido pelo navegador. Se o script do Web Worker usar muita memória, pode lançar um erro de "limite de memória excedido". Faça seu script lidar bem com grandes conjuntos de dados e evite vazamentos de memória.

Exemplo: Erro de script inexistente

// Script principal
const worker = new Worker('script-inexistente.js'); // Lança um erro

Exemplo: Erro comum de sintaxe no Web Worker

// worker.js
console.log('Olá do Web Worker') // Falta o ponto e vírgula

Exemplo: Limite de memória excedido no Web Worker

// worker.js
const dadosGrandes = new Array(1000000000); // Alocando um array grande

Ao tratar erros em Web Workers e ouvir eventos de erro, você pode lidar e se recuperar de erros, proporcionando uma melhor experiência do usuário e mantendo sua aplicação web estável.

Lembre-se de testar bem seus scripts de Web Worker e tratar erros tanto no script do Web Worker quanto no script principal. Um bom tratamento de erros garante que sua aplicação possa lidar com situações inesperadas e fornecer feedback útil ao usuário quando necessário.

Técnicas Avançadas de Web Worker

Web Workers oferecem técnicas avançadas que permitem estender sua funcionalidade e usá-los com outros recursos e APIs JavaScript. Vamos examinar algumas dessas técnicas avançadas.

Importar scripts em Web Workers é uma maneira de modularizar e reutilizar código dentro de um Web Worker. Usando a função importScripts(), você pode carregar arquivos JavaScript externos no escopo do Web Worker. Isso permite dividir seu código do worker em arquivos separados para melhor organização e manutenção.

Exemplo: Importando Scripts em Web Workers

// worker.js
importScripts('utility.js', 'math.js');

self.onmessage = function(event) {
    // Use funções ou variáveis dos scripts importados
    const result = performCalculation(event.data);
    self.postMessage(result);
};

Usar Web Workers com promessas é outra técnica poderosa. Promessas fornecem uma maneira de lidar com operações assíncronas e melhorar a legibilidade do código. Ao envolver a comunicação entre o script principal e o Web Worker dentro de promessas, você pode criar um fluxo de dados mais estruturado e intuitivo.

Exemplo: Usando Web Workers com Promessas

// Script principal
function performTask(data) {
    return new Promise((resolve, reject) => {
        const worker = new Worker('worker.js');
        worker.onmessage = function(event) {
            resolve(event.data);
        };
        worker.onerror = function(error) {
            reject(error);
        };
        worker.postMessage(data);
    });
}

// Uso
performTask('Dados da tarefa')
    .then(result => {
        console.log('Resultado da tarefa:', result);
    })
    .catch(error => {
        console.error('Erro na tarefa:', error);
    });

Combinar Web Workers com outras APIs abre novas possibilidades. Por exemplo, você pode usar Web Workers com a API de Arquivo para realizar tarefas de processamento de arquivos em segundo plano. Isso permite ler e modificar arquivos sem bloquear a thread principal.

Exemplo: Combinando Web Workers com API de Arquivo

// Script principal
const fileInput = document.getElementById('fileInput');
fileInput.addEventListener('change', function(event) {
    const file = event.target.files[0];
    const worker = new Worker('fileWorker.js');
    worker.onmessage = function(event) {
        console.log('Arquivo processado:', event.data);
    };
    worker.postMessage(file);
});

// fileWorker.js
self.onmessage = function(event) {
    const file = event.data;
    // Processa o arquivo no Web Worker
    // ...
    self.postMessage('Processamento do arquivo concluído');
};

Melhores Práticas para Web Workers

Ao usar Web Workers, é importante seguir as melhores práticas para obter o máximo deles e evitar armadilhas comuns. Aqui estão alguns pontos-chave a considerar:

Consideração Descrição
Quando usar Web Workers - Use Web Workers para tarefas intensivas em CPU que podem ser executadas em segundo plano.
- Evite usar Web Workers para tarefas que necessitam de comunicação frequente com a thread principal.
- Considere a sobrecarga de criar e gerenciar Web Workers para tarefas pequenas ou de curta duração.
Desempenho - Tenha cuidado com a quantidade de dados sendo transferidos entre a thread principal e os Web Workers.
- Use objetos transferíveis (ArrayBuffer, MessagePort) para evitar copiar grandes quantidades de dados entre threads.
- Minimize a frequência de troca de mensagens para reduzir a sobrecarga de comunicação.
Segurança - Certifique-se de que os scripts do Web Worker sejam servidos da mesma origem que o script principal para evitar problemas de segurança de origem cruzada.
- Tenha cuidado ao usar importScripts() para carregar scripts externos, pois eles podem introduzir vulnerabilidades de segurança.
- Valide e sanitize quaisquer dados recebidos dos Web Workers para prevenir ataques de injeção.

Exemplos de Web Worker

Web Workers oferecem uma maneira de mover tarefas do thread principal e melhorar o desempenho de aplicações web. Vamos ver alguns exemplos que mostram o uso de Web Workers em diferentes situações.

Exemplo Simples de Web Worker

// Script principal (main.js)
const worker = new Worker('worker.js');
worker.onmessage = function(event) {
  console.log('Resultado recebido do worker:', event.data);
};
worker.postMessage(5);

// Script do Worker (worker.js)
self.onmessage = function(event) {
  const number = event.data;
  const result = fibonacci(number);
  self.postMessage(result);
};

function fibonacci(n) {
  if (n <= 1) {
    return n;
  }
  return fibonacci(n - 1) + fibonacci(n - 2);
}

Neste exemplo simples, o script principal cria um novo Web Worker e envia um número para ele usando postMessage(). O script do worker recebe a mensagem, calcula o número de Fibonacci usando uma função recursiva e envia o resultado de volta ao script principal usando postMessage(). O script principal registra o resultado recebido no console.

Exemplo Complexo de Web Worker

// Script principal (main.js)
const worker = new Worker('worker.js');
worker.onmessage = function(event) {
  const { type, data } = event.data;
  if (type === 'progress') {
    updateProgressBar(data.progress);
  } else if (type === 'result') {
    displayResult(data.result);
  }
};
worker.postMessage({ type: 'start', data: { /* dados complexos */ } });

// Script do Worker (worker.js)
self.onmessage = function(event) {
  const { type, data } = event.data;
  if (type === 'start') {
    processData(data);
  }
};

function processData(data) {
  // Realiza processamento complexo de dados
  for (let i = 0; i < data.length; i++) {
    // Processa item de dados
    const progress = ((i + 1) / data.length) * 100;
    self.postMessage({ type: 'progress', data: { progress } });
  }
  const result = { /* dados processados */ };
  self.postMessage({ type: 'result', data: { result } });
}

Neste exemplo mais complexo, o script principal envia uma mensagem ao worker com um tipo e dados específicos. O script do worker recebe a mensagem, verifica o tipo de mensagem e executa a ação apropriada. Neste caso, quando o tipo de mensagem é 'start', o worker processa os dados recebidos. Durante o processamento, o worker envia atualizações de progresso de volta ao script principal usando postMessage(). Quando o processamento é concluído, o worker envia o resultado final de volta ao script principal. O script principal trata as mensagens recebidas com base em seus tipos, atualizando a barra de progresso ou exibindo o resultado.

Casos de uso reais para Web Workers

Caso de Uso Descrição
Processamento de Dados em Segundo Plano Web Workers podem ser usados para processar grandes conjuntos de dados ou realizar cálculos complexos em segundo plano sem afetar a responsividade do thread principal. Isso é útil para aplicações que lidam com análise de dados, processamento de imagens ou simulações científicas.
Requisições de API Assíncronas Web Workers podem ser usados para fazer requisições de API assíncronas e lidar com as respostas sem bloquear o thread principal. Isso melhora o desempenho percebido das aplicações web, permitindo que a interface do usuário permaneça responsiva enquanto aguarda respostas da API.
Atualizações em Tempo Real Web Workers podem ser usados para implementar atualizações em tempo real em aplicações web. Por exemplo, um Web Worker pode ser responsável por receber atualizações de um servidor através de WebSocket ou long-polling e notificar o thread principal para atualizar a interface do usuário. Isso mantém o thread principal livre para lidar com interações do usuário.
Pré-processamento de Dados para Visualização Web Workers podem ser usados para pré-processar grandes conjuntos de dados antes de visualizá-los no thread principal. Isso pode envolver filtragem, agregação ou transformação dos dados para reduzir a carga de trabalho no thread principal e melhorar o desempenho de renderização de gráficos ou tabelas.

Estes são apenas alguns exemplos de como os Web Workers podem ser usados em cenários reais. A flexibilidade e o poder dos Web Workers os tornam valiosos para mover tarefas, realizar processamento em segundo plano e melhorar o desempenho geral das aplicações web.