Un caso de uso concreto de un trabajador web

En el último módulo, se presentó una descripción general de los trabajadores web. Los trabajadores web pueden mejorar la capacidad de respuesta de las entradas si mueves JavaScript del subproceso principal a subprocesos de trabajo web separados, lo que puede ayudar a mejorar la Interacción a la siguiente pintura (INP) de tu sitio web cuando tienes trabajo que no necesita acceso directo al subproceso principal. Sin embargo, no basta con solo una descripción general y, en este módulo, se ofrece un caso de uso concreto para un trabajador web.

Un ejemplo de esto podría ser un sitio web que necesita quitar los metadatos EXIF de una imagen. Este no es un concepto tan disparatado. De hecho, los sitios web como Hulu ofrecen a los usuarios una forma de ver los metadatos EXIF para obtener detalles técnicos sobre 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 ArrayBuffer y extraer los metadatos EXIF podría ser potencialmente costosa si se realiza por completo en el subproceso principal. Afortunadamente, el alcance del trabajador web permite realizar este trabajo fuera del subproceso principal. Luego, a través de la canalización de mensajería del trabajador web, los metadatos de Exif se transmiten 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 hacemos este trabajo sin un trabajador web. Para hacerlo, sigue estos pasos:

  1. Abre una nueva pestaña en Chrome y, luego, las Herramientas para desarrolladores.
  2. Abre el panel de rendimiento.
  3. Navega a https://exif-worker.glitch.me/without-worker.html.
  4. En el panel de 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 contenga metadatos EXIF) en el campo y haz clic en el botón Get that JPEG!.
  6. Una vez que la interfaz se propague con los metadatos EXIF, vuelve a hacer clic en Record para detener la grabación.
El generador de perfiles de rendimiento muestra la actividad de la app del extractor de metadatos de imágenes que se produce por completo en el subproceso principal. Existen dos tareas largas sustanciales: 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 de extracción de metadatos de imágenes. Ten en cuenta que toda la actividad ocurre en el subproceso principal.

Ten en cuenta que, además de otros subprocesos que puedan estar presentes, como los subprocesos del rasterizador, etcétera, todo lo que incluye 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 fetch para obtener la parte 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 EXIF de la imagen.
  4. Los metadatos se extraen para construir una cadena HTML, que luego propaga el visor de metadatos.

Ahora, contrasta 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 ve el proceso para extraer los metadatos EXIF de un archivo JPEG en el subproceso principal, observa cómo se ve cuando hay un trabajador web en la mezcla:

  1. Abre otra pestaña en Chrome y abre las Herramientas para desarrolladores.
  2. Abre el panel de rendimiento.
  3. Navega a https://exif-worker.glitch.me/with-worker.html.
  4. En el panel de 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 Obtener ese JPEG!.
  6. Una vez que la interfaz se propague con los metadatos de Exif, vuelve a hacer clic en el botón de grabación para detener la grabación.
El generador de perfiles de rendimiento muestra la actividad de la app del extractor de metadatos de imágenes que se produce en el subproceso principal y en el de un trabajador web. Si bien aún existen tareas largas en el subproceso principal, son mucho más cortas, ya que la recuperación/decodificación de imágenes y la extracción de metadatos se realizan por completo en un subproceso del trabajador web. El único trabajo en el subproceso principal consiste en pasar datos desde y hacia el trabajador web.
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 donde se realiza la mayor parte del trabajo.

Esta es la potencia de un trabajador web. En lugar de hacer todo en el subproceso principal, todo excepto propagar el visor de metadatos con HTML se realiza en un subproceso separado. Esto significa que el subproceso principal se libera para realizar otras tareas.

Quizás la mayor ventaja 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. Es decir, el costo de descargar, analizar y compilar la secuencia de comandos exif-reader se realiza fuera del subproceso principal.

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

Observa el código del trabajador web

No es suficiente ver la diferencia que hace un trabajador web, sino que también es útil comprender (al menos en este caso) cómo se ve ese código para que sepas lo que es posible dentro del alcance del trabajador web.

Comienza con el código del subproceso principal que debe ocurrir antes de que el trabajador web pueda ingresar a la imagen:

// 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 trabajador web. Desde allí, el código del trabajador web comienza con una declaración importScripts que carga la secuencia de comandos exif-reader externa y, luego, configura la canalización de mensajería en 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 bit de JavaScript configura la canalización de mensajería 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 allí, 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 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://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('')
      });
    });
  });
});

Aunque es un poco de lectura, este también es un caso de uso bastante complicado para los trabajadores web. Sin embargo, vale la pena el trabajo por los resultados y no solo se limita a este caso de uso. Puedes usar trabajadores web para todo tipo de acciones, como aislar llamadas fetch y procesar respuestas, procesar grandes cantidades de datos sin bloquear el subproceso principal.

Cuando mejores el rendimiento de tus aplicaciones web, comienza a pensar en cualquier cosa que se pueda hacer razonablemente en un contexto de trabajador web. Las ganancias pueden ser significativas y pueden generar una mejor experiencia del usuario en general en tu sitio web.