Un caso de uso concreto de un trabajador web

En el último módulo, se proporcionó una descripción general de los Web Workers. Los trabajadores web pueden mejorar la capacidad de respuesta de la entrada trasladando JavaScript del subproceso principal a subprocesos de trabajadores web separados, lo que puede ayudar a mejorar la Interacción con el próximo dibujo (INP) de tu sitio web cuando tienes trabajo que no necesita acceso directo al subproceso principal. Sin embargo, una descripción general por sí sola no es suficiente, y en este módulo, se ofrece un caso de uso concreto para un trabajador web.

Un caso de uso podría ser un sitio web que necesita quitar los metadatos Exif de una imagen, lo que no es un concepto tan descabellado. De hecho, sitios web como Flickr ofrecen a los usuarios una forma de ver los metadatos Exif para conocer los detalles técnicos de las imágenes que alojan, como la profundidad de color, la marca y el modelo de la cámara, y otros datos.

Sin embargo, la lógica para recuperar una imagen, convertirla en un ArrayBuffer y extraer los metadatos de Exif podría ser potencialmente costosa si se realiza por completo en el subproceso principal. Afortunadamente, el alcance del Web Worker permite que este trabajo se realice fuera del subproceso principal. Luego, con la canalización de mensajes del trabajador web, los metadatos de Exif se transmiten de vuelta al subproceso principal como una cadena HTML y se muestran al usuario.

Cómo se ve el subproceso principal sin un trabajador web

Primero, observa cómo se ve el subproceso principal cuando realizamos este trabajo sin un subproceso web. Para hacerlo, sigue estos pasos:

  1. Abre una nueva pestaña en Chrome y abre sus Herramientas para desarrolladores.
  2. Abre el panel de rendimiento.
  3. Navega a https://chrome.dev/learn-performance-exif-worker/without-worker.html.
  4. En el panel Rendimiento, haz clic en Record en la esquina superior derecha del panel de Herramientas para desarrolladores.
  5. Pega este vínculo de imagen (o cualquier otro que elijas y que contenga metadatos Exif) en el campo y haz clic en el botón ¡Obtén ese JPEG!.
  6. Una vez que la interfaz se complete con los metadatos de Exif, vuelve a hacer clic en Grabar para detener la grabación.
El generador de perfiles de rendimiento que muestra la actividad del extractor de metadatos de la imagen que se produce por completo en el subproceso principal. Hay dos tareas largas importantes: una que ejecuta una recuperación para obtener la imagen solicitada y decodificarla, y otra que extrae los metadatos de la imagen.
Actividad del subproceso principal en la app del extractor de metadatos de imágenes. Ten en cuenta que toda la actividad se produce en el subproceso principal.

Ten en cuenta que, además de otros subprocesos que pueden estar presentes, como los subprocesos del rasterizador, etc., todo en la app ocurre en el subproceso principal. En el subproceso principal, sucede lo siguiente:

  1. El formulario toma la entrada y envía una solicitud de fetch para obtener la porción inicial de la imagen que contiene los metadatos Exif.
  2. Los datos de la imagen se convierten en un ArrayBuffer.
  3. La secuencia de comandos exif-reader se usa para extraer los metadatos de Exif de la imagen.
  4. Los metadatos se extraen para construir una cadena HTML, que luego completa el visor de metadatos.

Ahora, compara eso con una implementación del mismo comportamiento, pero con un trabajador web.

Cómo se ve el subproceso principal con un trabajador web

Ahora que viste cómo se extraen los metadatos Exif de un archivo JPEG en el subproceso principal, observa cómo se ve cuando se incluye un trabajador web:

  1. Abre otra pestaña en Chrome y abre sus Herramientas para desarrolladores.
  2. Abre el panel de rendimiento.
  3. Navega a https://chrome.dev/learn-performance-exif-worker/with-worker.html.
  4. En el panel Rendimiento, haz clic en el botón de grabación en la esquina superior derecha del panel de Herramientas para desarrolladores.
  5. Pega este vínculo de imagen en el campo y haz clic en el botón Get that JPEG!.
  6. Una vez que la interfaz se complete con los metadatos Exif, vuelve a hacer clic en el botón de grabación para detener la grabación.
El generador de perfiles de rendimiento que muestra la actividad de la app del extractor de metadatos de imágenes que se produce tanto en el subproceso principal como en un subproceso de trabajador web. Si bien todavía hay tareas largas en el subproceso principal, son mucho más cortas, ya que la recuperación y decodificación de imágenes, y la extracción de metadatos se realizan por completo en un subproceso de trabajo web. El único trabajo del subproceso principal consiste en pasar datos hacia el trabajador web y desde él.
Actividad del subproceso principal en la app del extractor de metadatos de imágenes. Ten en cuenta que hay un subproceso de trabajador web adicional en el que se realiza la mayor parte del trabajo.

Este es el poder de un trabajador web. En lugar de hacer todo en el subproceso principal, todo, excepto completar el visor de metadatos con HTML, se hace en un subproceso independiente. Esto significa que el subproceso principal queda libre para realizar otro trabajo.

Quizás la mayor ventaja aquí es que, a diferencia de la versión de esta app que no usa un trabajador web, la secuencia de comandos exif-reader no se carga en el subproceso principal, sino en el subproceso del trabajador web. Esto significa que el costo de descargar, analizar y compilar la secuencia de comandos exif-reader se realiza fuera del subproceso principal.

Ahora, profundicemos en el código del trabajador web que hace que todo esto sea posible.

Un vistazo al código del trabajador web

No basta con ver la diferencia que genera un trabajador web, sino que también ayuda a comprender, al menos en este caso, cómo se ve ese código para que sepas qué es posible en el alcance del trabajador web.

Comienza con el código del subproceso principal que debe ocurrir antes de que el trabajador web pueda entrar en escena:

// 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';
});

Este código se ejecuta en el subproceso principal y configura el formulario para enviar la URL de la imagen al subproceso de trabajador. A partir de ahí, el código del trabajador web comienza con una sentencia importScripts que carga la secuencia de comandos exif-reader externa y, luego, configura la canalización de mensajes para el subproceso 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);
  });
});

Este fragmento de JavaScript configura la canalización de mensajes para que, cuando el usuario envíe el formulario con una URL a un archivo JPEG, la URL llegue al trabajador web. A partir de ahí, el siguiente fragmento de código extrae los metadatos Exif del archivo JPEG, compila una cadena HTML y envía ese HTML de vuelta al window para que, finalmente, se muestre al usuario:

// 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://chrome.dev/learn-performance-exif-worker/js/with-worker/exif-worker.js
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('')
      });
    });
  });
});

Es un poco largo, pero este también es un caso de uso bastante complejo para los trabajadores web. Sin embargo, los resultados valen la pena, y no solo se limitan a este caso de uso. Puedes usar trabajadores web para todo tipo de tareas, como aislar llamadas fetch y procesar respuestas, procesar grandes cantidades de datos sin bloquear el subproceso principal, y eso es solo el comienzo.

Cuando mejore el rendimiento de sus aplicaciones web, comience a pensar en todo lo que se puede hacer de manera razonable en un contexto de trabajador web. Las ganancias podrían ser significativas y generar una mejor experiencia del usuario general en tu sitio web.