Um caso de uso concreto de worker da Web

No último módulo, apresentamos uma visão geral dos web workers. Os web workers podem melhorar a capacidade de resposta de entrada movendo o JavaScript da linha de execução principal para sequências de execução separadas do web worker, o que pode ajudar a melhorar a interação do seu site ao Next Paint (INP) quando há um trabalho que não precisa de acesso direto ao linha de execução principal. No entanto, uma visão geral por si só não é suficiente, e, neste módulo, é oferecido um caso de uso concreto para um funcionário da Web.

Um desses casos de uso pode ser um site que precisa remover metadados Exif de uma imagem. Esse conceito não é tão difícil de descobrir. Na verdade, sites como o Flickr oferecem aos usuários uma maneira de visualizar os metadados Exif para aprender detalhes técnicos sobre o imagens que hospedam, como profundidade de cor, marca e modelo da câmera e outros dados.

No entanto, a lógica para buscar uma imagem e convertê-la em um ArrayBuffer e a extração dos metadados Exif pode ser potencialmente cara se feita totalmente na linha de execução principal. Felizmente, o escopo do web worker permite que esse trabalho seja feito fora da linha de execução principal. Em seguida, usando o pipeline de mensagens do worker da Web, Os metadados EXIF são transmitidos de volta para a linha de execução principal como uma string HTML. exibidos ao usuário.

Como é a linha de execução principal sem um worker da Web

Primeiro, observe como fica a linha de execução principal quando fazemos esse trabalho sem uma worker da Web. Para fazer isso, siga estas etapas:

  1. Abra uma nova guia no Chrome e acesse as DevTools.
  2. Abra o painel de desempenho.
  3. Acesse https://exif-worker.glitch.me/without-worker.html.
  4. No painel de desempenho, clique em Record no canto superior direito do no painel DevTools.
  5. Cole este link de imagem ou outro de sua escolha que contenha Exif metadados no campo e clique no botão Get that JPEG!.
  6. Quando a interface for preenchida com metadados Exif, clique novamente em Record para pare a gravação.
.
O criador de perfil de desempenho mostrando a atividade do app extrator de metadados de imagem ocorrendo inteiramente na linha de execução principal. Há duas tarefas longas substanciais: uma que executa uma busca para obter a imagem solicitada e decodificá-la, e outra que extrai os metadados da imagem.
Atividade de linha de execução principal no app extrator de metadados de imagem. Observe que todas a atividade ocorre na linha de execução principal.

Exceto por outros threads que podem estar presentes, como um rasterizador linhas de execução e assim por diante, tudo no app ocorre na linha de execução principal. Na rede principal encadeamento, acontece o seguinte:

  1. O formulário recebe a entrada e envia uma solicitação fetch para conseguir o da imagem que contém os metadados Exif.
  2. Os dados da imagem são convertidos em ArrayBuffer.
  3. O script exif-reader (link em inglês) é usado para extrair os metadados EXIF do imagem.
  4. Os metadados são raspados para construir uma string HTML, que preenche o visualizador de metadados.

Compare isso com uma implementação do mesmo comportamento, mas usando uma interface trabalhador!

Como é a linha de execução principal com um worker da Web.

Agora que você sabe como extrair os metadados Exif de uma JPEG no encadeamento principal, dê uma olhada em como fica quando um servidor worker está na combinação:

  1. Abra outra guia no Chrome e abra as DevTools.
  2. Abra o painel de desempenho.
  3. Acesse https://exif-worker.glitch.me/with-worker.html.
  4. No painel de desempenho, clique no botão de gravação no canto superior direito. do painel do DevTools.
  5. Cole este link da imagem no campo e clique no botão Obter esse JPEG!.
  6. Depois que a interface for preenchida com metadados Exif, clique no botão de registro. novamente para interromper a gravação.
.
O criador de perfil de desempenho mostrando a atividade do app extrator de metadados de imagem ocorrendo na linha de execução principal e em uma linha de execução de worker da Web. Embora ainda haja tarefas longas na linha de execução principal, elas são substancialmente mais curtas, com a busca/decodificação de imagens e a extração de metadados ocorrendo inteiramente em uma linha de execução de worker da Web. O único trabalho de linha de execução principal envolve a transmissão de dados de e para o web worker.
Atividade de linha de execução principal no app extrator de metadados de imagem. Observe que há uma linha de execução extra de worker da Web em que a maior parte do trabalho é feita.

Esse é o poder de um web worker. Em vez de fazer tudo no , tudo menos o preenchimento do visualizador de metadados com HTML é feito em um em uma linha de execução separada. Isso significa que a linha de execução principal fica liberada para outras tarefas.

Talvez a maior vantagem aqui seja que, ao contrário da versão desse aplicativo que não usar um worker da Web, o script exif-reader não será carregado na interface principal linha de execução, mas na linha de execução do web worker. Isso significa que o custo o download, a análise e a compilação do script exif-reader ocorrem fora da linha de execução principal.

Agora vamos analisar o código do web worker que torna tudo isso possível.

Uma análise do código do web worker

Não basta ver a diferença que um web worker faz, mas também ajudar a entender, pelo menos neste caso, como é o código para que você saiba o que possível no escopo do web worker.

Comece com o código da linha de execução principal que precisa ocorrer antes que o worker da Web possa insira a imagem:

// scripts.js

// Register the Exif reader web worker:
const exifWorker = new Worker('/js/with-worker/exif-worker.js');

// We have to send image requests through this proxy due to CORS limitations:
const imageFetchPrefix = 'https://res.cloudinary.com/demo/image/fetch/';

// Necessary elements we need to select:
const imageFetchPanel = document.getElementById('image-fetch');
const imageExifDataPanel = document.getElementById('image-exif-data');
const exifDataPanel = document.getElementById('exif-data');
const imageInput = document.getElementById('image-url');

// What to do when the form is submitted.
document.getElementById('image-form').addEventListener('submit', event => {
  // Don't let the form submit by default:
  event.preventDefault();

  // Send the image URL to the web worker on submit:
  exifWorker.postMessage(`${imageFetchPrefix}${imageInput.value}`);
});

// This listens for the Exif metadata to come back from the web worker:
exifWorker.addEventListener('message', ({ data }) => {
  // This populates the Exif metadata viewer:
  exifDataPanel.innerHTML = data.message;
  imageFetchPanel.style.display = 'none';
  imageExifDataPanel.style.display = 'block';
});

Esse código é executado na linha de execução principal e configura o formulário para enviar o URL da imagem à o web worker. A partir daí, o código do worker da Web começa com uma importScripts. que carrega o script exif-reader externo e configura a o pipeline de mensagens para a linha de execução principal:

// exif-worker.js

// Import the exif-reader script:
importScripts('/js/with-worker/exifreader.js');

// Set up a messaging pipeline to send the Exif data to the `window`:
self.addEventListener('message', ({ data }) => {
  getExifDataFromImage(data).then(status => {
    self.postMessage(status);
  });
});

Esse bit de JavaScript configura o pipeline de mensagens para que, quando o usuário envia o formulário com um URL para um arquivo JPEG, o URL chega no web worker. A partir daí, o próximo trecho de código extrai os metadados Exif do arquivo JPEG cria uma string HTML e envia esse HTML de volta ao window para ser exibido ao usuário:

// Takes a blob to transform the image data into an `ArrayBuffer`:
// NOTE: these promises are simplified for readability, and don't include
// rejections on failures. Check out the complete web worker code:
// https://glitch.com/edit/#!/exif-worker?path=js%2Fwith-worker%2Fexif-worker.js%3A10%3A5
const readBlobAsArrayBuffer = blob => new Promise(resolve => {
  const reader = new FileReader();

  reader.onload = () => {
    resolve(reader.result);
  };

  reader.readAsArrayBuffer(blob);
});

// Takes the Exif metadata and converts it to a markup string to
// display in the Exif metadata viewer in the DOM:
const exifToMarkup = exif => Object.entries(exif).map(([exifNode, exifData]) => {
  return `
    <details>
      <summary>
        <h2>${exifNode}</h2>
      </summary>
      <p>${exifNode === 'base64' ? `<img src="data:image/jpeg;base64,${exifData}">` : typeof exifData.value === 'undefined' ? exifData : exifData.description || exifData.value}</p>
    </details>
  `;
}).join('');

// Fetches a partial image and gets its Exif data
const getExifDataFromImage = imageUrl => new Promise(resolve => {
  fetch(imageUrl, {
    headers: {
      // Use a range request to only download the first 64 KiB of an image.
      // This ensures bandwidth isn't wasted by downloading what may be a huge
      // JPEG file when all that's needed is the metadata.
      'Range': `bytes=0-${2 ** 10 * 64}`
    }
  }).then(response => {
    if (response.ok) {
      return response.clone().blob();
    }
  }).then(responseBlob => {
    readBlobAsArrayBuffer(responseBlob).then(arrayBuffer => {
      const tags = ExifReader.load(arrayBuffer, {
        expanded: true
      });

      resolve({
        status: true,
        message: Object.values(tags).map(tag => exifToMarkup(tag)).join('')
      });
    });
  });
});

É um pouco de leitura, mas esse também é um caso de uso bastante complexo para web workers. No entanto, os resultados valem o trabalho e não se limitam apenas a este caso de uso. Você pode usar os web workers para todo tipo de coisa, como isolar chamadas fetch e processamento de respostas, processamento de grandes volumes de dados sem bloquear linha de execução principal, e isso é só para começar.

Ao melhorar o desempenho de seus aplicativos da Web, comece a pensar qualquer coisa que possa ser razoavelmente feita em um contexto de web worker. Os ganhos podem ser significativa e pode resultar em uma melhor experiência do usuário em seu site.