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

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

Прежде чем мы начнем, давайте углубимся и посмотрим, что такое веб-воркеры в целом.

Что такое веб-воркеры?

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

Вы можете создать веб-работника в коде приложения следующим образом:

const myAwesomeWorker = new Worker('/path/to/worker.js');

Затем вы можете общаться с работником:

myAwesomeWorker.postMessage('Hi there, love you');

Внутри веб-воркера вы можете принять сообщение и ответить вызывающему абоненту:

// /path/to/worker.js

onmessage = function (e) {
  // do some magic here
  postMessage('I am all done, back to you, main application code.');
};

Затем вызывающий абонент может прослушивать сообщения от веб-воркера:

myAwesomeWorker.onmessage = function (e) {
  console.log('Message received from myAwesomeWorker');
  console.log(e.data);
};

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

Теперь, когда мы разобрались с основами, давайте посмотрим, что такое сервис-воркеры.

Что такое сервисные работники?

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

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

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

Еще одним вариантом использования может быть возможность использовать Push API для получения уведомлений с сервера. Этот API предоставляет разработчикам возможность доставлять асинхронные сообщения и обновления пользователям, подписавшимся на них, что обеспечивает лучшее взаимодействие со своевременным новым контентом. Например, вы можете создать сервис-воркер, который будет обновлять пользователей при появлении нового приложения.

Хорошо, теперь, когда мы понимаем концепцию сервис-воркеров, давайте посмотрим, каков типичный жизненный цикл сервис-воркера.

Жизненный цикл сервисного работника

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

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/myServiceWorker.js');
}

Во-первых, мы делаем простую проверку, чтобы увидеть, поддерживаются ли сервис-воркеры в браузере пользователя. Затем мы вызываем navigator.serviceWorker.register, указав путь к нашему файлу JavaScript сервис-воркера.

Затем мы должны различать два варианта использования:

1. Когда мы впервые регистрируем сервис-воркер
2. Когда мы обновляем существующий сервис-воркер

1. Регистрация сервис-воркера в первый раз

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

Подводя итог первому случаю, жизненный цикл выглядит следующим образом:

1. Загрузите (проанализируйте и выполните)
2. Установите
3. Активируйте

2. Обновление существующего сервис-воркера

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

Теперь рабочему процессу нужно дождаться, когда перестанут загружаться какие-либо страницы, которые все еще используют старый сервисный работник. Как только больше не остается страниц для загрузки, новый сервис-воркер активируется (становится активным работником). Вы можете пропустить ожидание нового работника, используя ServiceWorkerGlobalScope.skipWaiting().

Итак, когда сервис-воркер обновляется, вот как выглядит его жизненный цикл:

1. Загрузите
2. Установите
3. Подождите
4. Активируйте

Мы говорили о том, как зарегистрировать сервис-воркер, и как браузеры потом его активируют. Но чем заканчивается срок службы сервисного работника?

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

В целом жизненный цикл сервис-воркера можно показать на одной диаграмме:

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

Использование сервис-воркера для кэширования запросов

Одним из наиболее распространенных вариантов использования сервис-воркеров является то, что они используются для кэширования ресурсов и запросов на вашем веб-сайте. Они являются фундаментальной частью, если вы хотите создать прогрессивное веб-приложение (PWA). Как мы упоминали ранее, при регистрации сервис-воркер действует как прослойка между вашим сайтом и сетью. Работник перехватывает каждый запрос с вашего сайта в мир.

Сервисный работник не только является связующим звеном между вашим веб-сайтом и миром, но и может использовать Cache Storage API. В этом примере мы будем использовать именно это — простой сервис-воркер, кэширующий запросы в кеше браузера. Этот API работает аналогично стандартному кешу браузера, но зависит от вашего домена. У вас есть полный контроль над API, то есть вы можете контролировать срок действия ключей и добавлять новые ключи. Важно отметить, что хранилище сервис-воркера не зависит от HTTP-кэша браузера, чтобы мы не смешивали их.

Без дальнейших церемоний, давайте рассмотрим, как мы можем кэшировать наши ресурсы с помощью сервис-воркеров.

Кэширование ресурсов при установке сервис-воркера

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

const registerWorker = async () => {
  if ('serviceWorker' in navigator) {
    try {
      const registration = await navigator.serviceWorker.register('/worker.js');
      console.log('Service worker registration succeeded:', registration);
    } catch (error) {
      console.error(`Registration failed with ${error}`);
    }
  }
};

registerWorker();

Во-первых, мы проверим, поддерживаются ли сервис-воркеры в контексте, где работает наш код, с проверкой «serviceWorker» в навигаторе. После этого мы выполним navigator.serviceWorker.register(‘/worker.js’), который зарегистрирует файл worker.js в качестве нашего сервис-воркера. После этого мы напишем в консоль браузера, чтобы убедиться, что все настроено правильно. Если бы где-то произошла ошибка, наш блок try/catch позаботился бы об этом.

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

navigator.serviceWorker.register('worker.js', { scope: '/some-scope' });

Если вы опустите область действия, будет использоваться значение по умолчанию. Значение области действия по умолчанию зависит от того, где зарегистрирован сервис-воркер. Если вы зарегистрируете сервис-воркер под domain.com/index.html, он будет контролировать domain.com/index.html и все страницы под ним. Если вы измените область действия на что-то другое, как показано ниже:

navigator.serviceWorker.register('worker.js', { scope: '/blog/' });

А если вы зарегистрируете сервис-воркер в домене domain.com/index.html, он будет контролировать только его часть domain.com/blog/.

Отлично, теперь, когда мы зарегистрировали нашего сервис-воркера и поняли концепцию его области действия — давайте определим некоторую логику в файле сервис-воркера worker.js. Вот как это будет выглядеть:

const version = 'v1';

const addResourcesToCache = async (resources) => {
  const cache = await caches.open(version);
  await cache.addAll(resources);
};

self.addEventListener('install', (event) => {
  console.log(`${version} installing...`);

  event.waitUntil(
    addResourcesToCache([
      '/',
      '/index.html',
      '/styles.css',
      '/script.js',
      '/jungle.png',
    ])
  );
});

Вверху мы сначала объявляем версию как v1 — мы доберемся до этого через секунду. Затем мы определяем функцию addResourcesToCache, которая открывает экземпляр Cache Storage и записывает в него:

const cache = await caches.open(version);
await cache.addAll(resources);

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

Итак, если мы сейчас откроем нашу страницу, сервис-воркер, определенный в _worker.js_, установит и кэширует наши ресурсы. Чтобы убедиться, что это произошло, мы можем проверить вкладку «Приложения» в Google Chrome. Если мы откроем там раздел «Хранилище», мы увидим, сколько места занимает наш сайт:

Кроме того, мы можем зайти в «Хранилище кеша» и проверить кеш v1, который мы только что создали в нашем сервис-воркере. В «Cache Storage» вы должны увидеть указанные нами ресурсы:

Круто, мы получили активы нашего веб-сайта в кеше сервис-воркера, вдали от стандартного кеша браузера. Но что теперь, спросите вы? Теперь нам нужно указать браузеру обслуживать эти активы из кеша. И отличный способ сделать это — использовать нашего сервис-воркера.

Обслуживание ресурсов из кеша

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

self.addEventListener('fetch', (event) => {
  event.respondWith(caches.match(event.request));
});

С помощью этого кода мы перехватываем каждый запрос с нашего сайта. Затем мы вызываем respondWith и отвечаем кэшированным значением в нашем кэше. Если мы добавим этот код, закройте, а затем снова откройте наш веб-сайт и откройте вкладку «Сеть» в браузере. Мы увидим, что ресурсы кэшируются сервис-воркером:

Это все прекрасно и модно, но если мы добавим ресурс на наш сайт или удалим ресурс из кеша вручную, наш сайт может сломаться.

Например, я попытался удалить styles.css из кеша в разделе «Приложение» → «Хранилище кеша», и CSS пропал при следующем открытии страницы.
Ни worker, ни браузер снова извлек styles.css из-за логики в рабочем потоке. Чтобы смягчить это, мы должны убедиться, что запрос проходит через нашего сервис-воркера, если в нашем кеше нет соответствующего ресурса. Вот как мы можем это сделать:

const cacheFirst = async (request) => {
  const responseFromCache = await caches.match(request);

  if (responseFromCache) {
    return responseFromCache;
  }

  return fetch(request);
};

self.addEventListener('fetch', (event) => {
  event.respondWith(cacheFirst(event.request));
});

С новой функцией cacheFirst мы уверены, что запрос, которого нет в кеше, будет отправлен в сеть с помощью fetch(request).

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

const putInCache = async (request, response) => {
  const cache = await caches.open(version);

  if (request.method !== 'GET') {
    console.log('Cannot cache non-GET requests');
    return;
  }

  await cache.put(request, response);
};

const cacheFirst = async (request) => {
  const responseFromCache = await caches.match(request);

  if (responseFromCache) {
    return responseFromCache;
  }

  const responseFromNetwork = await fetch(request);

  // we need to clone the response because the response stream can only be read once
  putInCache(request, responseFromNetwork.clone());

  return responseFromNetwork;
};

self.addEventListener('fetch', (event) => {
  event.respondWith(cacheFirst(event.request));
});

Здесь мы определяем функцию putInCache, которая помещает запрос и его ответ в кеш.

Если вы заметили, мы также проверяем метод запроса. По дизайну мы не можем кэшировать не-GET-запросы в Cache Storage API. После этого мы изменили функцию cacheFirst, чтобы она вызывала функцию putInCache с объектом запроса и клоном ответа. Мы должны клонировать ответ, потому что потоки запросов и ответов можно прочитать только один раз. Затем мы возвращаем исходный ответ браузеру.

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

Кэш-Первый

Здесь мы реализовали подход «сначала кеш», при котором мы сначала пытаемся обслужить запрос из кеша, если он доступен.

Сеть-First

Вы можете попробовать реализовать сетевой подход, когда мы обычно отправляем запрос в сеть и кэшируем его. Затем, когда запрос не может быть выполнен (например, сайт потерял связь) — мы обслуживаем неудавшийся запрос из кеша.

Stale-While-Revalidate

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

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

Работники сферы услуг против других работников

Теперь, когда у нас есть представление о сервис-воркерах, мы можем рассмотреть другие типы веб-воркеров и то, чем они отличаются друг от друга. Начнем с того, насколько схожи веб-воркеры и сервис-воркеры.

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

  • Оба они работают в разных потоках, не блокируя основной поток JavaScript и пользовательский интерфейс.
  • Они не могут напрямую взаимодействовать с DOM и имеют ограниченный доступ к API браузера.
  • Они оба веб-работники. Но сервис-воркеры — это всего лишь специализированная версия веб-воркеров.

И различия между ними:

  • Как мы упоминали ранее, сервис-воркеры позволяют вам перехватывать сетевые запросы (через событие fetch) и прослушивать события Push API в фоновом режиме (через push событие). Веб-работники не могут этого сделать.
  • Страница может порождать несколько веб-воркеров, но только один сервис-воркер контролирует все активные вкладки в области, в которой он был зарегистрирован.
  • Срок жизни веб-воркера тесно связан с вкладкой, к которой он принадлежит, а жизненный цикл сервис-воркера не зависит от нее. Когда вы закроете вкладку, на которой запущен веб-воркер, он будет завершен. Service Worker может продолжать работать в фоновом режиме, даже если на сайте, который его зарегистрировал, нет открытых активных вкладок.
  • Сервисные работники работают только через HTTPS из соображений безопасности. Имея возможность изменять сетевые запросы, они широко открыты для атак человек посередине, поэтому они разрешены только для защищенных соединений. В Firefox API-интерфейсы Service Worker также скрыты и не могут использоваться, когда пользователь находится в режиме приватного просмотра. Для этого есть открытая ошибка, если вы хотите ее отследить.

Есть и совместители. Это рабочие процессы, которые могут использоваться несколькими сценариями, работающими в разных окнах, фреймах и т. д., если они находятся в том же домене, что и рабочий процесс. Скрипты должны обмениваться данными через активный порт, поэтому они более сложны, чем стандартные рабочие или рабочие службы.

Помимо этого, вы также можете использовать ворклеты. Интерфейс ворклета — это облегченная версия веб-воркеров, предоставляющая разработчикам доступ к низкоуровневым частям конвейера рендеринга. Ворклеты можно использовать для запуска кода JavaScript и WebAssembly для выполнения рендеринга графики или обработки звука, где требуется высокая производительность.

Подведение итогов

Фу, какая поездка. Мы изучили основы сервис-воркеров, но давайте еще раз пройдемся по ним. Вот что нужно помнить:

  1. Service worker — это специализированный worker, работающий вне основного потока JavaScript.
  2. Сервисный работник может действовать как промежуточное ПО между вашим сайтом и сетью.
  3. Чтобы сервис-воркер был на вашем сайте, его нужно сначала скачать, установить, а затем активировать.
  4. Если вы обновляете существующий сервис-воркер, предыдущий сервис-воркер должен быть выгружен со страницы (или вместо этого вы можете пропустить это ожидание).
  5. Сервисные работники работают только через HTTPS из соображений безопасности.
  6. Одним из основных вариантов использования сервис-воркера является кэширование ресурсов.

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

Если вам интересен код из этого сообщения в блоге, я отправил его в репозиторий на GitHub. Вы можете проверить это там, там есть небольшая страница index.html, которая демонстрирует, как вы можете кэшировать ресурсы.

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

До тех пор, спасибо за чтение, и поймаем вас в следующем.

Первоначально опубликовано Николой Джузой в блоге Uploadcare