Вероятно, большинство из вас так или иначе слышали о Service Worker в JavaScript, которые являются ключевой частью современной веб-разработки. В последние годы они набирают обороты, и всё это благодаря популярности прогрессивных веб-приложений или PWA.
Когда следует использовать Service Worker, в каком контексте их можно применять? Для начала можно ознакомиться с некоторыми основами.
Что такое Service Worker?
По сути, это скрипт, который запускается в фоновом режиме браузера. Он полностью независим от веб-страницы, с которой он работает или которую обслуживает.
На самом деле Service Worker действует как прокси-сервер, который находится между веб-приложением, браузером и сетью. Он даёт веб-приложению возможность работать как нативное приложение.
Моменты, на которые стоит обратить внимание
Service Worker не может получить прямой доступ к DOM. Он использует механизм сообщений, отправляемых через интерфейс postMessage.
Завершает выполнение, когда не используется. Это означает, что он управляется событиями.
Использует механизм promise ES6
Используют только протокол HTTPS. Нужно использовать веб-сервер, который работает с HTTPS.
Как работает Service Worker?
По сути, он даёт возможность приложению перехватывать сетевые запросы, кэшировать эти запросы для повышения производительности приложения. Это полезное улучшение, потому что контент кэшируется браузером, но это немного другой уровень кэширования.
Одна картинка лучше тысячи слов, поэтому для лучшего понимания стоит посмотреть на следующее изображение.
Жизненный цикл Service Worker
Как упоминалось ранее, Service Worker работает независимо от страницы, которой он управляет. Если нужно установить Service Worker в веб-приложение, первое, что нужно сделать, это зарегистрировать его.
После этого окно браузера, запускающее установку Service Worker, находится в фоновом режиме.
Наиболее распространенные варианты использования
Теперь стоит рассмотреть случаи практического применения Service Worker.
Поддержка кэширования
Service Worker может быть использован в рамках ряда стратегий кэширования. Рассмотрим варианты, как это можно использовать:
- Только кэш — когда есть статический контент, который никогда не меняется.
- Сеть или кеш — когда надо показать пользователю самый актуальный контент с единственным условием/желанием, чтобы он загружался быстрее.
- Кэширование и обновление — когда надо мгновенно показать контент, и вы не возражаете против временной синхронизации с сервером.
- Кэшировать, обновлять и обновлять — когда надо мгновенно показать контент, одновременно получая новый контент в фоновом режиме. Как только новый контент станет доступен, его можно будет показать.
Web push
Web push позволяет приложению отправлять push-уведомления и возвращать содержимое после получения уведомления.
- Push and retrieve payload — когда надо вернуть контент после того, как он будет доставлен.
- Push payload — когда надо доставлять не только текст, но и различные виды данных, обогащая ими сообщение.
- Push rich — когда надо показать изображение, вибрацию и все, что обозначает сообщение, которое надо доставить.
- Push-clients — когда надо показывать разные уведомления в зависимости от состояния приложения.
Более сложные варианты применения
API для аналитики
Веб-приложению можно добавить возможность отслеживать его использование и время от времени пользоваться API синхронизации для загрузки собранных данных.
Балансировщик нагрузки
Веб-приложение динамически выбирает лучшего поставщика контента в зависимости от доступности серверов. В этом случае нужен Service Worker, который перехватывает запрос к ресурсам и выбирает наиболее подходящего поставщика контента в зависимости от его доступности.
Практические примеры и многое другое о том, как использовать Service Worker можно посмотреть на ServiceWorke.rs.
Переходим к практике
Регистрация Service Worker
Как уже было сказано выше в описании жизненного цикла Service Worker, сперва нужно его установить. Первый шаг — регистрация.
/**
* Registering the Service Worker (SW)
*/
//Check support for the SW
if ("serviceWorker" in navigator) {
console.log("Service worker supported");
//registered once the page is loaded
//so, we add the load event Listener
window.addEventListener("load", () => {
//Registering the service worker
navigator.serviceWorker
.register("../service-worker.js")
.then(registration => {
//Registration was succesfully
console.log(
`Service Worker succesfully registered, scope ${registration.scope}`
);
})
.catch(error => {
//Registration failed
console.log(`There was an error while registering: ${error}`);
});
});
}
После этого можно проверять, что Service Worker работает chrome://inspect/#service-workers
, так:
Кроме того, если открыть Chrome Developer tool и перейти к вкладке Application tab -> Service Workers, можно увидеть всю информацию о состоянии Service Worker.
Установка Service Worker
Самый простой пример, который нужно сделать, это кэшировать все файлы. Вообще, можно даже определить, какие файлы следует кэшировать. Итак, это можно сделать на этапе установки.
//cache name
const CACHE_NAME = "mullinstack.com-v2";
//we want to cache the next files
const cacheAssets = ["index.html", "about.html", "js/main.js"];
//Install event
self.addEventListener("install", e => {
console.log("Service Worker Installed");
e.waitUntil(
caches
.open(CACHE_NAME)
.then(cache => {
console.log("From service worker:caching files");
cache.addAll(cacheAssets);
})
.then(() => {
self.skipWaiting();
})
);
});
В приведенном выше коде происходит следующее:
- Определение имени для кэша (
mullinstack.com-v2
) - Определение файлов, которые будут кэшироваться. Здесь будет массив.
- Внутри
install eventListener
браузеру сообщается о необходимости подождать, пока обещание (promise) не будет выполнено, и внутри этой функции объявляется кэш, который будет существовать под именемmullinstack.com-v2
. - Наконец, все заявленные ранее файлы добавляются в кэш.
Удаление ненужных кэшей
Теперь можно удалить все старые версии кэша, которые больше не нужны.
//Active event
self.addEventListener("activate", e => {
console.log("Service Worker activated");
e.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cache => {
if (cache !== CACHE_NAME) {
console.log("Cleaning up old cache");
return caches.delete(cache);
}
})
);
})
);
});
Получение ответа
Всё вышеперечисленное не имеет значения, если не вернуть кэшированный контент. Итак, как это сделать.
Это возможно внутри обработчика события fetch
, которое можно обработать так:
//Fetch event
self.addEventListener("fetch", e => {
console.log("fetching cached content");
e.respondWith(
fetch(e.request)
.then(res => {
const copyCache = res.clone();
caches.open(cacheName).then(cache => {
cache.put(e.request, copyCache);
});
return res;
})
.catch(error => caches.match(e.request).then(res => res))
);
});