Ouvintes de eventos e trabalhadores da Web

Recentemente, descobri a API Web Workers . É lamentável que eu não tenha gastado tempo com essa ferramenta bem suportada antes. Os aplicativos da web modernos exigem muito dos recursos do principal encadeamento de execução JavaScript. Isso afeta a produtividade dos projetos e sua capacidade de garantir uma experiência conveniente ao usuário. Atualmente, os profissionais da Web são exatamente o que pode ajudar um desenvolvedor a criar projetos da Web rápidos e convenientes.



No momento que eu entendi tudo


Trabalhadores da Web têm muitas qualidades positivas. Mas realmente percebi sua utilidade quando me deparei com uma situação em que um determinado aplicativo usa vários ouvintes de eventos DOM. Como eventos de envio de formulários, redimensionamento de janelas, cliques nos botões. Todos esses ouvintes devem funcionar no segmento principal. Se o encadeamento principal estiver sobrecarregado com determinadas operações que demoram muito para serem concluídas, isso terá um efeito ruim na taxa de reação dos ouvintes de eventos às influências do usuário. O aplicativo "diminui a velocidade", os eventos aguardam o lançamento do encadeamento principal.

Devo admitir que a razão pela qual eu estava tão interessada em ouvintes de eventos é que inicialmente entendi mal quais tarefas os web-workers são projetados para resolver. No começo, pensei que eles poderiam ajudar a melhorar a velocidade da execução do código. Eu pensei que um aplicativo seria capaz de fazer muito mais em um determinado período de tempo, caso alguns fragmentos de seu código fossem executados em paralelo, em threads separados. Porém, durante a execução do código do projeto da web, uma situação é bastante comum quando, antes de você começar a fazer algo, é necessário aguardar algum evento. Digamos que o DOM precise ser atualizado somente após a conclusão de alguns cálculos. Sabendo disso, eu acreditava ingenuamente que, se, em qualquer caso, tiver que esperar, significa que não faz sentido transferir a execução de algum código para um encadeamento separado.

Aqui está um exemplo de código que você pode lembrar aqui:

const calculateResultsButton = document.getElementById('calculateResultsButton'); const openMenuButton = document.getElementById('#openMenuButton'); const resultBox = document.getElementById('resultBox'); calculateResultsButton.addEventListener('click', (e) => {     // "    -, ,   ,     //   DOM   ?"     const result = performLongRunningCalculation();     resultBox.innerText = result; }); openMenuButton.addEventListener('click', (e) => {     //      . }); 

Aqui, atualizo o texto no campo após a conclusão de alguns cálculos, presumivelmente longos. Parece inútil executar esse código em um thread separado, pois o DOM não é atualizado antes da conclusão desse código. Como resultado, é claro, eu decido que esse código precisa ser executado de forma síncrona. No entanto, vendo esse código, a princípio não entendi que, enquanto o encadeamento principal estiver bloqueado, outros ouvintes de eventos não serão iniciados. Isso significa que os "freios" começam a aparecer na página.

Como "desacelerar" a página


Aqui está um projeto CodePen demonstrando o acima.


Um projeto demonstrando uma situação em que as páginas são lentas

Pressionar o botão Freeze faz com que o aplicativo comece a resolver a tarefa síncrona. Tudo isso leva 3 segundos (simula o desempenho de cálculos demorados). Se, ao mesmo tempo, clicar no botão Increment , até que decorridos 3 segundos, o valor no campo Click Count não será atualizado. Um novo valor correspondente ao número de cliques no Increment será gravado nesse campo somente após três segundos. O fluxo principal é bloqueado durante uma pausa. Como resultado, tudo na janela do aplicativo parece inoperante. A interface do aplicativo está congelada. Eventos que surgem no processo de “congelamento” aguardam a oportunidade de usar os recursos do fluxo principal.

Se você clicar em Freeze e tentar resize me! , novamente, até que três segundos se passaram, o tamanho do campo não será alterado. E depois disso, o tamanho do campo, no entanto, mudará, mas não há necessidade de falar sobre nenhuma "suavidade" na interface.

Os ouvintes de eventos são um fenômeno muito maior do que parece à primeira vista


Qualquer usuário não gostará de trabalhar com um site que se comporte como mostrado no exemplo anterior. Mas aqui apenas alguns ouvintes de eventos são usados ​​aqui. No mundo real, estamos falando de escalas completamente diferentes. Decidi usar o método getEventListeners no Chrome e, usando o script a seguir, descubra o número de ouvintes de eventos anexados aos elementos DOM de várias páginas. Este script pode ser executado diretamente no console da ferramenta do desenvolvedor. Aqui está:

 Array  .from([document, ...document.querySelectorAll('*')])  .reduce((accumulator, node) => {    let listeners = getEventListeners(node);    for (let property in listeners) {      accumulator = accumulator + listeners[property].length    }    return accumulator;  }, 0); 

Eu executei esse script em páginas diferentes e descobri o número de ouvintes de eventos usados ​​neles. Os resultados da minha experiência são mostrados na tabela a seguir.
App
Número de ouvintes de eventos
Dropbox
602
Mensagens do Google
581
Reddit
692
YouTube
6054 (!!!)

Você não pode prestar atenção a números específicos. O principal aqui é que estamos falando de um número muito grande de ouvintes de eventos. Como resultado, se pelo menos uma operação de longa duração no aplicativo der errado, todos esses ouvintes deixarão de responder às influências do usuário. Isso oferece aos desenvolvedores várias maneiras de perturbar os usuários de seus aplicativos.

Livre-se dos "freios" com trabalhadores da Web


Dado todo o exposto, vamos reescrever o exemplo anterior. Aqui está sua nova versão. Parece exatamente como o antigo, mas por dentro está organizado de maneira diferente. Nomeadamente, agora a operação que costumava bloquear o encadeamento principal foi movida para seu próprio encadeamento. Se você fizer o mesmo com este exemplo como no anterior, poderá notar diferenças positivas sérias. Ou seja, se depois de clicar no botão Freeze , clicar em Increment , o campo Click Count Cliques será atualizado (depois que o trabalhador da Web terminar, em qualquer caso, o número 1 será adicionado ao valor Click Count Cliques). O mesmo vale para resize me! . O código em execução em um encadeamento separado não bloqueia ouvintes de eventos. Isso permite que todos os elementos da página permaneçam operacionais, mesmo durante a execução de uma operação que anteriormente simplesmente “congelou” a página.

Aqui está o código JS para este exemplo:

 const button1 = document.getElementById('button1'); const button2 = document.getElementById('button2'); const count = document.getElementById('count'); const workerScript = `  function pause(ms) {    let time = new Date();    while ((new Date()) - time <= ms) {}               }  self.onmessage = function(e) {    pause(e.data);    self.postMessage('Process complete!');  } `; const blob = new Blob([  workerScript, ], {type: "text/javascipt"}); const worker = new Worker(window.URL.createObjectURL(blob)); const bumpCount = () => {  count.innerText = Number(count.innerText) + 1; } worker.onmessage = function(e) {  console.log(e.data);  bumpCount(); } button1.addEventListener('click', async function () {  worker.postMessage(3000); }); button2.addEventListener('click', function () {  bumpCount(); }); 

Se você se aprofundar um pouco nesse código, perceberá que, embora a API dos Web Workers possa ser organizada e mais confortável, não há nada de particularmente assustador ao trabalhar com ele. Esse código provavelmente parece assustador devido ao fato de ser uma demonstração simples e rapidamente escrita. Para melhorar a usabilidade da API e facilitar o trabalho com trabalhadores da Web, você pode usar algumas ferramentas adicionais. Por exemplo, o seguinte me pareceu interessante:

  • Workerize - permite executar módulos em trabalhadores da Web.
  • Greenlet - possibilita a execução de partes arbitrárias de código assíncrono em trabalhadores da Web.
  • Comlink - fornece uma camada conveniente de abstração pela API Web Workers.

Sumário


Se seu aplicativo da Web for um projeto moderno típico, significa que é muito provável que tenha muitos ouvintes de eventos. Também é possível que, no thread principal, realize muitos cálculos, que podem ser realizados em outros threads. Como resultado, você pode fornecer um bom serviço para seus usuários e ouvintes de eventos, confiando cálculos "pesados" aos trabalhadores da Web.

Vale ressaltar que o entusiasmo excessivo pelos trabalhadores da Web e a remoção de tudo o que não está diretamente relacionado à interface do usuário com os trabalhadores da Web provavelmente não são a melhor idéia. Esse processamento de aplicativo pode exigir muito tempo e esforço, o código do projeto se tornará mais complicado e os benefícios dessa conversão serão muito pequenos. Em vez disso, pode valer a pena procurar um código verdadeiramente "pesado" e levá-lo aos profissionais da web. Com o tempo, a idéia de usar trabalhadores da Web se tornará mais familiar e você provavelmente será guiado por ela, mesmo no estágio do design da interface.

Seja como for, eu recomendo que você entenda a API Web Workers. Essa tecnologia possui amplo suporte ao navegador e os requisitos de desempenho dos aplicativos Web modernos estão aumentando. Portanto, não temos motivos para recusar o estudo de ferramentas como trabalhadores da Web.

Caros leitores! Você usa trabalhadores da web em seus projetos?


Source: https://habr.com/ru/post/pt479268/


All Articles