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

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

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

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

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

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

  1. Откройте новую вкладку в Chrome и откройте ее DevTools.
  2. Откройте панель производительности .
  3. Перейдите по адресу https://chrome.dev/learn-performance-exif-worker/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://chrome.dev/learn-performance-exif-worker/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://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('')
      });
    });
  });
});

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

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