Конкретный вариант использования веб-работника

В последнем модуле был дан обзор веб-воркеров . Веб-работники могут улучшить скорость реагирования на ввод, переместив JavaScript из основного потока в отдельные потоки веб-работников, что может помочь улучшить взаимодействие вашего веб-сайта с следующей отрисовкой (INP), когда у вас есть работа, которая не требует прямого доступа к основному потоку. Однако одного обзора недостаточно, и в этом модуле предлагается конкретный вариант использования веб-работника.

Одним из таких вариантов использования может быть веб-сайт, которому необходимо удалить метаданные Exif из изображения — это не такая уж надуманная концепция. Фактически, такие веб-сайты, как Flickr, предлагают пользователям возможность просматривать метаданные Exif, чтобы узнать технические подробности о размещаемых на них изображениях, такие как глубина цвета, марка и модель камеры, а также другие данные.

Однако логика получения изображения, его преобразования в ArrayBuffer и извлечения метаданных Exif может быть потенциально дорогостоящей, если выполняться полностью в основном потоке. К счастью, область веб-воркера позволяет выполнять эту работу вне основного потока. Затем, используя конвейер обмена сообщениями веб-работника, метаданные Exif передаются обратно в основной поток в виде строки HTML и отображаются пользователю.

Как выглядит основной поток без веб-воркера

Во-первых, посмотрите, как выглядит основной поток, когда мы выполняем эту работу без веб-воркера. Для этого выполните следующие действия:

  1. Откройте новую вкладку в Chrome и откройте ее DevTools.
  2. Откройте панель производительности .
  3. Перейдите по адресу https://exif-worker.glitch.me/without-worker.html .
  4. На панели производительности нажмите «Запись» в правом верхнем углу панели DevTools.
  5. Вставьте ссылку на это изображение (или другое по вашему выбору, содержащее метаданные Exif) в поле и нажмите кнопку «Получить этот JPEG!». кнопка.
  6. Как только интерфейс заполнится метаданными Exif, снова нажмите «Запись» , чтобы остановить запись.
Профилировщик производительности, показывающий активность приложения для извлечения метаданных изображений, происходящую полностью в основном потоке. Существует две существенные длинные задачи: одна выполняет выборку для получения запрошенного изображения и его декодирования, а другая извлекает метаданные из изображения.
Активность основного потока в приложении для извлечения метаданных изображений. Обратите внимание, что все действия происходят в основном потоке.

Обратите внимание, что — за исключением других потоков, которые могут присутствовать, таких как потоки растеризации и т. д. — все в приложении происходит в основном потоке. В основном потоке происходит следующее:

  1. Форма принимает входные данные и отправляет запрос fetch чтобы получить начальную часть изображения, содержащую метаданные Exif.
  2. Данные изображения преобразуются в ArrayBuffer .
  3. Скрипт exif-reader используется для извлечения метаданных Exif из изображения.
  4. Метаданные очищаются для создания строки HTML, которая затем заполняется средством просмотра метаданных.

Теперь сравните это с реализацией того же поведения, но с использованием веб-воркера!

Как выглядит основной поток у веб-воркера

Теперь, когда вы увидели, как выглядит извлечение метаданных Exif из файла JPEG в основном потоке, взгляните, как это выглядит, когда в этом участвует веб-воркер:

  1. Откройте другую вкладку в Chrome и откройте ее DevTools.
  2. Откройте панель производительности .
  3. Перейдите по адресу https://exif-worker.glitch.me/with-worker.html .
  4. На панели производительности нажмите кнопку записи в правом верхнем углу панели DevTools.
  5. Вставьте ссылку на это изображение в поле и нажмите « Получить этот JPEG!». кнопка.
  6. Как только интерфейс заполнится метаданными Exif, снова нажмите кнопку записи, чтобы остановить запись.
Профилировщик производительности, показывающий активность приложения для извлечения метаданных изображений, происходящую как в основном потоке, так и в рабочем веб-потоке. Хотя в основном потоке по-прежнему выполняются длинные задачи, они существенно короче: получение/декодирование изображений и извлечение метаданных полностью выполняются в рабочем веб-потоке. Единственная работа основного потока включает в себя передачу данных веб-воркеру и обратно.
Активность основного потока в приложении для извлечения метаданных изображений. Обратите внимание, что существует дополнительный рабочий веб-поток, в котором выполняется большая часть работы.

В этом сила веб-работника. Вместо того, чтобы делать все в основном потоке, все, кроме заполнения средства просмотра метаданных HTML, выполняется в отдельном потоке. Это означает, что основной поток освобождается для выполнения другой работы.

Возможно, самым большим преимуществом здесь является то, что, в отличие от версии этого приложения, которая не использует веб-воркер, сценарий exif-reader загружается не в основной поток, а в рабочий веб-поток. Это означает, что затраты на загрузку, анализ и компиляцию сценария exif-reader выполняются вне основного потока.

Теперь углубимся в код веб-работника, который делает все это возможным!

Взгляните на код веб-работника

Недостаточно увидеть разницу, которую вносит веб-воркер, это также помогает понять — по крайней мере, в этом случае — как выглядит этот код, чтобы вы знали, что возможно в области веб-воркера.

Начните с кода основного потока, который должен произойти, прежде чем веб-воркер сможет ввести изображение:

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

Этот код выполняется в основном потоке и настраивает форму для отправки URL-адреса изображения веб-воркеру. Далее код веб-воркера начинается с оператора importScripts , который загружает внешний скрипт exif-reader , а затем настраивает конвейер обмена сообщениями для основного потока:

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

Этот фрагмент JavaScript настраивает конвейер обмена сообщениями так, что когда пользователь отправляет форму с URL-адресом в файл JPEG, URL-адрес поступает в веб-воркер. Далее следующий фрагмент кода извлекает метаданные Exif из файла JPEG, создает строку HTML и отправляет этот HTML обратно в window , чтобы в конечном итоге отобразить его пользователю:

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

Это немного сложно читать, но это также довольно сложный вариант использования для веб-работников. Однако результаты стоят затраченных усилий, и они не ограничиваются этим вариантом использования. Вы можете использовать веб-воркеры для самых разных целей, например, для изоляции вызовов fetch и обработки ответов, обработки больших объемов данных без блокировки основного потока — и это только для начала.

При повышении производительности ваших веб-приложений начните думать обо всем, что можно разумно сделать в контексте веб-работника. Выигрыш может быть значительным и может привести к общему улучшению пользовательского опыта вашего веб-сайта.