Веб-разработчики или все, кто занимается веб-разработкой, могут сказать вам, что они знают о «воркерах» в браузерах. Однако проблема в том, что большинство людей подразумевают либо веб-воркеров в целом, либо сервис-воркеров (или даже общих воркеров), когда говорят «воркеры». Эти термины могут сбивать с толку, особенно если вы с ними не сталкивались.
Вот почему сегодня мы погрузимся в сервисные работники. Мы увидим, почему вы хотите использовать сервис-воркеры, что они из себя представляют, как они работают, и объясним разницу между сервис-воркерами и другими работниками в браузере.
Прежде чем мы начнем, давайте углубимся и посмотрим, что такое веб-воркеры в целом.
Что такое веб-воркеры?
Веб-работник — это фоновая задача, определяемая с помощью скрипта — файла 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 для выполнения рендеринга графики или обработки звука, где требуется высокая производительность.
Подведение итогов
Фу, какая поездка. Мы изучили основы сервис-воркеров, но давайте еще раз пройдемся по ним. Вот что нужно помнить:
- Service worker — это специализированный worker, работающий вне основного потока JavaScript.
- Сервисный работник может действовать как промежуточное ПО между вашим сайтом и сетью.
- Чтобы сервис-воркер был на вашем сайте, его нужно сначала скачать, установить, а затем активировать.
- Если вы обновляете существующий сервис-воркер, предыдущий сервис-воркер должен быть выгружен со страницы (или вместо этого вы можете пропустить это ожидание).
- Сервисные работники работают только через HTTPS из соображений безопасности.
- Одним из основных вариантов использования сервис-воркера является кэширование ресурсов.
Что касается веб-воркеров, то они в основном используются для делегирования работы из основного потока, когда страница открыта. Как только страница закрывается, классический воркер также закрывается.
Если вам интересен код из этого сообщения в блоге, я отправил его в репозиторий на GitHub. Вы можете проверить это там, там есть небольшая страница index.html, которая демонстрирует, как вы можете кэшировать ресурсы.
Это все, ребята, в следующий раз мы рассмотрим, как отлаживать сервис-воркеры (хотя мы показали, где вы можете это сделать), и мы можем показать, как использовать сервис-воркеры в некоторых других случаях использования.
До тех пор, спасибо за чтение, и поймаем вас в следующем.
Первоначально опубликовано Николой Джузой в блоге Uploadcare