Articles

Roscar a web com os trabalhadores dos módulos

JavaScript é simples, o que significa que só pode realizar uma operação de cada vez. Isto é intuitivo e funciona bem para muitos casos na web, mas pode tornar-se problemático quando precisamos fazer tarefas pesadas de elevação como processamento de dados, processamento, computação ou análise. Como aplicações cada vez mais complexas são entregues na web, há uma maior necessidade de processamento multi-roscado.

na plataforma web, o primitivo principal para a roscagem e paralelismo é a API dos trabalhadores da Web. Os trabalhadores são uma abstração leve em cima de threads do sistema operacional que expõem uma API de passagem de mensagem para a comunicação inter-thread. Isto pode ser imensamente útil ao realizar cálculos dispendiosos ou ao operar em grandes conjuntos de dados, permitindo que o fio principal funcione sem problemas enquanto executa as operações caras em um ou mais threads de fundo.

Aqui está um exemplo típico de Uso do trabalhador, onde um script do trabalhador escuta mensagens do tópico principal e responde enviando mensagens de volta de sua própria página:

page.js:

const worker = new Worker('worker.js');
worker.addEventListener(e => {
console.log(e.data);
});
worker.postMessage('hello');

trabalhador.js:

addEventListener('message', e => {
if (e.data === 'hello') {
postMessage('world');
}
});

a API do Web Worker está disponível na maioria dos navegadores há mais de dez anos. Embora isso signifique que os trabalhadores têm um excelente suporte de navegador e estão bem otimizados, isso também significa que eles há muito tempo antecedem módulos JavaScript. Uma vez que não havia um sistema de módulos quando os trabalhadores foram projetados, a API para carregar código em um trabalhador e compor scripts tem permanecido semelhante ao script síncrono abordagem de carregamento de script comum em 2009.

History: classic workers #

The Worker constructor takes a classic script URL, which is relative to the document URL. Ele imediatamente retorna uma referência à nova instância do trabalhador, que expõe uma interface de mensagens, bem como um terminate() método que pára imediatamente e destrói o trabalhador.

const worker = new Worker('worker.js');

An importScripts() função está disponível dentro dos trabalhadores da web para carregar o código adicional, mas ele interrompe a execução do trabalhador a fim de obter e avaliar cada script. Ele também executa scripts no escopo global como um id clássico<script> tag, significando que as variáveis em um script podem ser reescritas pelas variáveis em outro.trabalhador.js:

importScripts('greet.js');
// ^ could block for seconds
addEventListener('message', e => {
postMessage(sayHello());
});

greet.js:

// global to the whole worker
function sayHello() {
return 'world';
}

por esta razão, os trabalhadores da web têm historicamente imposto um efeito outsized sobre a arquitetura de uma aplicação. Os desenvolvedores tiveram que criar ferramentas inteligentes e workarounds para tornar possível a utilização de Web workers sem desistir de práticas de desenvolvimento modernas. Como um exemplo, bundlers como webpack incorporaram uma pequena implementação de carregadores de módulos em código gerado que usa importScripts() para carregamento de código, mas envolve módulos em funções para evitar colisões variáveis e simular importações e exportações de dependências.

introduza os trabalhadores dos módulos #

um novo modo para os trabalhadores da web com os benefícios ergonómicos e de desempenho dos módulos JavaScript está a ser enviado no Chrome 80, denominado “trabalhadores dos módulos”. TheWorker constructor now accepts a new{type:"module"} option, which changes script loading and execution to match<script type="module">.

const worker = new Worker('worker.js', {
type: 'module'
});

Uma vez que os trabalhadores de módulos são módulos JavaScript padrão, eles podem usar declarações de importação e exportação. Como em todos os módulos JavaScript, dependências são executadas apenas uma vez em um determinado contexto (thread principal, worker, etc.), e todas as importações futuras referenciam a instância do módulo já executada. O carregamento e execução de módulos JavaScript também é otimizado por navegadores. As dependências de um módulo podem ser carregadas antes do Módulo ser executado, o que permite que as árvores de módulos inteiros sejam carregadas em paralelo. O carregamento do módulo também contém código processado, o que significa que os módulos que são usados no fio principal e em um trabalhador só precisam ser processados uma vez.

mover-se para módulos JavaScript também permite o uso de importação dinâmica para código de carga preguiçosa sem bloquear a execução do trabalhador. Importação dinâmica é muito mais explícita do que usar importScripts() para carregar dependências, uma vez que as exportações do módulo importado são devolvidas ao invés de depender de variáveis globais.trabalhador.js:

import { sayHello } from './greet.js';
addEventListener('message', e => {
postMessage(sayHello());
});

greet.js:

import greetings from './data.js';
export function sayHello() {
return greetings.hello;
}

para assegurar um bom desempenho, o antigo métodoimportScripts() não está disponível nos trabalhadores dos módulos. Mudar os trabalhadores para usar módulos JavaScript significa que todo o código é carregado em modo estrito. Outra mudança notável é que o valor de this no escopo de topo de um módulo JavaScript é undefined, enquanto nos trabalhadores clássicos o valor é o escopo global do trabalhador. Felizmente, sempre houve um self global que fornece uma referência ao escopo global. Está disponível em todos os tipos de trabalhadores, incluindo trabalhadores de serviços, bem como no DOM.

módulo workers also remove support for HTML-style comments. Sabia que podia usar comentários de HTML em scripts de web worker?uma melhoria substancial do desempenho que vem com os trabalhadores de módulos é a capacidade de pré-carregar trabalhadores e suas dependências. Com o módulo de trabalhadores, os scripts são carregados e executados como JavaScript padrão de módulos, o que significa que eles podem ser pré-carregados e até mesmo pré-analisados usando modulepreload:

<!-- preloads worker.js and its dependencies: -->
<link rel="modulepreload" href="worker.js">
<script>
addEventListener('load', () => {
// our worker code is likely already parsed and ready to execute!
const worker = new Worker('worker.js', { type: 'module' });
});
</script>

Pré-carregados os módulos podem também ser usadas por ambos o thread principal e o módulo de trabalhadores. Isto é útil para módulos que são importados em ambos os contextos, ou nos casos em que não é possível saber antecipadamente se um módulo será usado na linha principal ou em um trabalhador.

anteriormente, as opções disponíveis para pré-carregamento de scripts web worker eram limitadas e não necessariamente confiáveis. Classic workers had their own “worker” resource type for preloading, but no browsers implemented <link rel="preload" as="worker">. Como resultado, a técnica primária disponível para pré-carregamento de trabalhadores da web ERA usar <link rel="prefetch">, que dependia inteiramente do cache HTTP. Quando usado em combinação com os cabeçalhos de cache corretos, isso tornou possível evitar que a instanciação do trabalhador tenha que esperar para baixar o script do trabalhador. However, unlike modulepreload this technique did not support preloading dependencies or pre-parsing.

e os trabalhadores partilhados? #

trabalhadores compartilhados foram atualizados com suporte para módulos JavaScript a partir do Chrome 83. Como dedicado trabalhadores, a construção partilhada de trabalho com o {type:"module"} opção agora carrega o trabalhador script como um módulo em vez de um clássico de script:

const worker = new SharedWorker('/worker.js', {
type: 'module'
});

Antes de apoio do JavaScript módulos, o SharedWorker() construtor esperado apenas uma URL e um opcional name argumento. Isto continuará a funcionar para uso clássico do trabalhador partilhado; no entanto, a criação de trabalhadores partilhados por módulos requer a utilização do novo argumento options. As opções disponíveis são as mesmas que para um trabalhador dedicado, incluindo o name opção que substitui o anterior name argumento.e o trabalhador de serviços? #

a especificação de trabalhador de serviço já foi atualizada para suportar a aceitação de um módulo JavaScript como o ponto de entrada, usando o mesmo {type:"module"} opção como trabalhadores de módulos, no entanto esta mudança ainda tem de ser implementada em navegadores. Uma vez que isso aconteça, será possível instanciar um trabalhador de serviço usando um módulo JavaScript usando o seguinte código:

navigator.serviceWorker.register('/sw.js', {
type: 'module'
});

Agora que a especificação foi atualizada, navegadores estão começando a implementar o novo comportamento. Isso leva tempo, porque há algumas complicações adicionais associadas com trazer módulos JavaScript para o trabalhador de serviço. O registro de trabalhadores de Serviços precisa comparar scripts importados com suas versões anteriores em cache ao determinar se deve desencadear uma atualização, e isso precisa ser implementado para módulos JavaScript quando usado para trabalhadores de serviços. Além disso, os trabalhadores de serviços precisam ser capazes de contornar o cache para scripts em certos casos ao verificar para atualizações.