Como o JavaScript funciona: Os blocos de construção dos Web Workers + 5 casos quando você deve usá-los

Este é o post #7 da série dedicada a explorar o JavaScript e seus componentes de construção. No processo de identificação e descrição dos elementos centrais, também compartilhamos algumas regras práticas que usamos ao criar o SessionStack , um aplicativo JavaScript leve que precisa ser robusto e altamente eficiente para ajudar os usuários a ver e reproduzir seus defeitos de aplicativos da Web em tempo real. .

Abaixo estão os posts dessa série publicados até o momento

Desta vez, vamos desmontar os Web Workers: ofereceremos uma visão geral, discutiremos os diferentes tipos de trabalhadores, como seus componentes de construção passarão a funcionar juntos e quais vantagens e limitações eles oferecem em diferentes cenários. Por fim, forneceremos 5 casos de uso nos quais os Web Workers serão a escolha certa.

Você já deve estar familiarizado com o fato de que o JavaScript é executado em uma única thread, conforme discutimos anteriormente em grande detalhe.O JavaScript, no entanto, oferece aos desenvolvedores a oportunidade de escrever códigos assíncronos também.

Limitações da programação assíncrona

Já discutimos a programação assíncrona anteriormente e quando ela deve ser usada.

A programação assíncrona permite que a interface de usuário do aplicativo seja responsiva, “agendando” partes do código para serem executadas um pouco mais tarde no loop de eventos, permitindo que a renderização da interface do usuário seja executada primeiro.

Um bom caso de uso para programação assíncrona é fazer solicitações AJAX. Como as solicitações podem levar muito tempo, elas podem ser feitas de forma assíncrona e, enquanto o cliente aguarda uma resposta, outro código pode ser executado.

Isso, no entanto, representa um problema — as solicitações são tratadas pela API WEB do navegador, mas como outros códigos podem ser assíncronos? Por exemplo, e se o código que está dentro do retorno de chamada de sucesso fizer uso muito intenso da CPU:

Se o performCPUIntensiveCalculation não for uma solicitação HTTP, mas um código de bloqueio (por exemplo, um loop for enorme), não há como liberar o loop de eventos e desbloquear a interface do usuário - ele congelará e não responderá ao usuário.

Isso significa que as funções assíncronas resolvem apenas uma pequena parte das limitações de thread única da linguagem JavaScript.

Em alguns casos, você pode obter bons resultados ao desbloquear a interface do usuário de cálculos de execução mais longa usando setTimeout. Por exemplo, ao agrupar uma computação complexa em chamadas setTimeoutseparadas, você pode colocá-las em “locais” separados no loop de eventos e dessa forma, comprar tempo para a renderização/responsividade da UI ser executada.

Vamos dar uma olhada em uma função simples que calcula a média de um array numérico:

É assim que você pode reescrever o código acima e “emular” a assincronia:

Isso fará uso da função setTimeout , que adicionará cada etapa do cálculo mais abaixo no loop de eventos. Entre cada cálculo, haverá tempo suficiente para outros cálculos, necessários para descongelar o navegador.

Web Workers vai salvar o dia

O HTML5 nos trouxe muitas coisas excelentes, incluindo:

  • SSE (que descrevemos e comparamos com WebSockets em um post anterior )
  • Geolocalização
  • Cache de aplicativos
  • Armazenamento local
  • Arraste e solte
  • Web workers

Os Web Workers são encadeamentos no navegador que podem ser usados ​​para executar código JavaScript sem bloquear o loop de eventos.

Isso é realmente incrível. Todo o paradigma do JavaScript é baseado na idéia de ambiente single-threaded, mas aqui vêm os Web Workers que removem (parcialmente) essa limitação.

Os Web Workers permitem que os desenvolvedores coloquem tarefas de longa duração e computacionalmente intensivas em segundo plano sem bloquear a interface do usuário, tornando seu aplicativo ainda mais responsivo. Além do mais, não são necessários truques com o setTimeout para hackear o ciclo de eventos.

Aqui está uma demonstração simples que mostra a diferença entre classificar uma matriz com e sem Web Workers.

Visão geral dos Web Workers

Os Web Workers permitem que você faça coisas como ativar scripts de longa execução para lidar com tarefas que exigem muitos recursos computacionais, mas sem bloquear a interface do usuário. Na verdade, tudo acontece em paralelo. Os Web Workers são verdadeiramente multi-threaded.

Você pode dizer: “JavaScript não era uma linguagem de thread único?”

Este deve ser o seu ‘aha!’ momento em que você percebe que o JavaScript é uma linguagem, que não define um modelo de threading. Os Web Workers não fazem parte do JavaScript, são um recurso do navegador que pode ser acessado através do JavaScript. A maioria dos navegadores tem sido historicamente single-threaded (isso, obviamente, mudou), e a maioria das implementações JavaScript acontecem no navegador. Os Web Workers não são implementados no Node.JS — ele tem um conceito de “cluster” ou “child_process”, que é um pouco diferente.

Vale a pena notar que a especificação menciona três tipos de Web Workers:

Dedicated Workers

Os Web Workers dedicados são instanciados pelo processo principal e só podem se comunicar com ele.

Suporte ao Navegador de Trabalhadores Dedicados

Shared Workers

Os trabalhadores compartilhados podem ser acessados ​​por todos os processos em execução na mesma origem (diferentes guias do navegador, iframes ou outros trabalhadores compartilhados).

Suporte ao navegador de trabalhadores compartilhados

Service Workers

Um Service Worker é um trabalhador orientado a eventos registrado em relação a uma origem e um caminho. Ele pode controlar a página da Web/site a que está associado, interceptar e modificar as solicitações de navegação e recursos e armazenar em cache recursos de maneira muito granular para oferecer um excelente controle sobre como o aplicativo se comporta em determinadas situações (por exemplo, quando a rede não está acessível.)

Suporte ao navegador do Service Workers

Neste post, vamos nos concentrar nos Dedicated Workerse nos referir a eles como “Trabalhadores da Web” ou “Trabalhadores”.

Como funcionam os Web workers

Os Web Workers são implementados como arquivos .js incluídos por meio de solicitações HTTP assíncronas em sua página. Essas solicitações são completamente ocultadas pela API da Web Worker .

Os trabalhadores utilizam a passagem de mensagens parecidas com linhas para obter o paralelismo. Eles são perfeitos para manter sua interface do usuário atualizada, de alto desempenho e responsiva para os usuários.

Os Web Workers são executados em um thread isolado no navegador. Como resultado, o código que eles executam precisa estar contido em um arquivo separado . Isso é muito importante lembrar.

Vamos ver como um trabalhador básico é criado:

Se o arquivo “task.js” existir e estiver acessível, o navegador gerará um novo thread que baixará o arquivo de forma assíncrona. Logo após o download ser concluído, ele será executado e o trabalhador será iniciado. 
Caso o caminho fornecido para o arquivo retorne um 404, o trabalhador falhará silenciosamente.

Para iniciar o trabalhador criado, você precisa invocar o método postMessage:

Comunicação do Web Worker

Para se comunicar entre um Web Worker e a página que o criou, você precisa usar o método postMessage ou um Broadcast Channel .

O método postMessage

Os navegadores mais recentes suportam um objeto JSON como primeiro parâmetro para o método, enquanto os navegadores mais antigos suportam apenas uma string .

Vamos ver um exemplo de como a página que cria um trabalhador pode se comunicar com ele, passando um objeto JSON como um exemplo mais “complicado”. Passar uma string é o mesmo.

Vamos dar uma olhada na seguinte página HTML (ou parte dela para ser mais preciso):

E é assim que nosso script de trabalho será:

Quando o botão é clicado, postMessage será chamado a partir da página principal. A linha worker.postMessage transmite o objeto JSON ao worker, adicionando cmd e chaves de data com seus respectivos valores. O trabalhador manipulará essa mensagem por meio do manipulador de message definido.

Quando a mensagem chega, a computação real está sendo executada no worker, sem bloquear o loop de eventos. O trabalhador está verificando o evento passado e é executado exatamente como uma função JavaScript padrão. Quando estiver pronto, o resultado é passado de volta para a página principal.

No contexto de um trabalhador, tanto o self quanto this referenciam o escopo global para o trabalhador.

Há duas maneiras de parar um trabalhador: chamando worker.terminate() da página principal ou chamando self.close() dentro do próprio worker.

Broadcast Channel (Canal de Transmissão)

O Broadcast Channel é uma API mais geral para comunicação. Ele nos permite transmitir mensagens para todos os contextos que compartilham a mesma origem. Todas as guias do navegador, iframes ou trabalhadores atendidos da mesma origem podem emitir e receber mensagens:

E visualmente, você pode ver o que os canais de transmissão parecem para torná-lo mais claro:

O Broadcast Channel tem suporte de navegador mais limitado:

O tamanho das mensagens

Existem duas maneiras de enviar mensagens para os Web Workers:

  • Copiando a mensagem: a mensagem é serializada, copiada, enviada e, em seguida, desserializada na outra extremidade. A página e o worker não compartilham a mesma instância, portanto, o resultado final é que uma duplicata é criada em cada passagem. A maioria dos navegadores implementam esse recurso automaticamente codificando/decodificando JSON o valor em qualquer extremidade. Como esperado, essas operações de dados adicionam uma sobrecarga significativa à transmissão de mensagens. Quanto maior a mensagem, mais tempo demora para ser enviado.
  • Transferindo a mensagem: isso significa que o remetente original não pode mais usá-lo uma vez enviado. A transferência de dados é quase instantânea. A limitação é que somente o ArrayBuffer é transferível.

Recursos disponíveis para Web Workers

Os Web Workers têm acesso apenas a um subconjunto de recursos JavaScript devido à sua natureza multiencadeada. Aqui está a lista de recursos:

Limitações do Web Worker

Infelizmente, os Web Workers não têm acesso a alguns recursos JavaScript cruciais:

  • O DOM (não é seguro para threads)
  • O objeto da window
  • O objeto do document
  • O objeto parent

Isso significa que um Web Worker não pode manipular o DOM (e, portanto, a interface do usuário). Pode ser complicado às vezes, mas quando você aprender a usar corretamente os Web Workers, você começará a usá-los como “máquinas de computação” separadas, enquanto todas as alterações da interface do usuário ocorrerão no código da página. Os workers farão todo o trabalho pesado para você e, assim que os trabalhos estiverem concluídos, você passará os resultados para a página que faz as alterações necessárias na interface do usuário.

Manipulando Erros

Tal como acontece com qualquer código JavaScript, você vai querer lidar com quaisquer erros que são lançados em seus Web workers. Se ocorrer um erro enquanto um trabalhador estiver executando, o ErrorEvent será disparado. A interface contém três propriedades úteis para descobrir o que deu errado:

  • filename — o nome do script de trabalho que causou o erro
  • lineno — o número da linha em que ocorreu o erro
  • message — uma descrição do erro

Isto é um exemplo:

Aqui, você pode ver que criamos um worker e começamos a ouvir o evento de error .

Dentro do worker (em workerWithError.js ) criamos uma exceção intencional ao multiplicar x por 2, enquanto x não está definido nesse escopo. A exceção é propagada para o script inicial e onError está sendo chamado com informações sobre o erro.

Bons casos de uso para Web Workers

Até agora, listamos os pontos fortes e as limitações dos Web Workers. Vamos ver agora quais são os casos de uso mais fortes para eles:

  • Ray tracing : ray tracing é uma técnica de renderização para gerar uma imagem traçando o caminho da luz como pixels. O rastreamento de raios usa cálculos matemáticos muito intensivos de CPU para simular o caminho da luz. A idéia é simular alguns efeitos como reflexão, refração, materiais etc. Toda essa lógica computacional pode ser adicionada a um Web Worker para evitar o bloqueio do thread da interface do usuário. Melhor ainda — você pode facilmente dividir a renderização da imagem entre vários trabalhadores (e, respectivamente, entre várias CPUs). Aqui está uma simples demonstração do rastreamento de raios usando o Web Workers — https://nerget.com/rayjs-mt/rayjs.html .
  • Criptografia: a criptografia de ponta a ponta está ficando cada vez mais popular devido ao rigor crescente das regulamentações sobre dados pessoais e confidenciais. A criptografia pode ser algo muito demorado, especialmente se houver muitos dados que precisam ser criptografados com freqüência (antes de enviá-los ao servidor, por exemplo). Este é um cenário muito bom em que um Web Worker pode ser usado, uma vez que não requer acesso ao DOM ou nada sofisticado — são puros algoritmos fazendo o seu trabalho. Uma vez no funcionário, ele é perfeito para o usuário final e não afeta sua experiência.
  • Pré-busca de dados: para otimizar seu site ou aplicativo da web e melhorar o tempo de carregamento de dados, você pode aproveitar os Web Workers para carregar e armazenar alguns dados com antecedência, para que você possa usá-los posteriormente quando necessário. Os Web Workers são incríveis nesse caso, porque não afetam a interface do usuário do seu aplicativo, ao contrário de quando isso é feito sem funcionários.
  • Progressive web apps: eles precisam carregar rapidamente mesmo quando a conexão de rede está instável. Isso significa que os dados precisam ser armazenados localmente no navegador. É aqui que o IndexDB ou APIs similares entram em ação . Basicamente, é necessário um armazenamento no lado do cliente. Para ser usado sem bloquear o thread da interface do usuário, o trabalho precisa ser feito em Web Workers. Bem, no caso do IndexDB, existe uma API assíncrona que permite fazer isso mesmo sem funcionários, mas antes havia uma API síncrona (que poderia ser introduzida novamente), que só deveria ser usada dentro de trabalhadores.
  • Verificação ortográfica: um verificador ortográfico básico funciona da seguinte maneira — o programa lê um arquivo de dicionário com uma lista de palavras escritas corretamente. O dicionário está sendo analisado como uma árvore de pesquisa para tornar o texto real eficiente na pesquisa. Quando uma palavra é fornecida ao verificador, o programa verifica se ela existe na árvore de pesquisa pré-criada. Se a palavra não for encontrada na árvore, o usuário pode receber grafias alternativas, substituindo caracteres alternativos e testando se é uma palavra válida — se é a palavra que o usuário queria escrever. Todo esse processamento pode ser facilmente transferido para um Web Worker, de modo que o usuário possa digitar palavras e sentenças sem bloquear a interface do usuário, enquanto o funcionário realiza toda a pesquisa e fornecimento de sugestões.

Desempenho e confiabilidade são muito importantes para nós no SessionStack . A razão pela qual eles são tão importantes é que, uma vez que o SessionStack é integrado ao seu aplicativo da Web, ele começa a registrar tudo, desde alterações do DOM e interação do usuário até solicitações de rede, exceções não tratadas e mensagens de depuração. Todos esses dados são transmitidos aos nossos servidores em tempo real, o que permite que você repita problemas de seus aplicativos da Web como vídeos e veja tudo o que aconteceu com seus usuários. Tudo isso ocorre com latência mínima e sem sobrecarga de desempenho para seu aplicativo.

É por isso que estamos descarregando (em qualquer lugar que faça sentido) a lógica tanto da nossa biblioteca de monitoramento quanto do nosso reprodutor para Web Workers que estão lidando com tarefas muito intensivas de CPU, como hashing para validar integridade de dados, renderização etc.

As tecnologias da Web mudam e se desenvolvem constantemente, por isso, vamos a milha extra para garantir que o SessionStack seja muito leve e tenha impacto zero no desempenho dos aplicativos de nossos usuários.

Existe um plano gratuito se você quiser experimentar o SessionStack .

Referências

Este é um artigo traduzido com a autorização do autor. O artigo original pode ser lido em https://blog.sessionstack.com/how-javascript-works-the-building-blocks-of-web-workers-5-cases-when-you-should-use-them-a547c0757f6a

Autor do post original — Alexander Zlatkov— Co-founder & CEO @SessionStack