- PVSM.RU - https://www.pvsm.ru -
Всё чаще возникает задача научить frontend-приложение работать в автономном режиме. Это значит придать web-приложению свойство mobile- или desktop-программы — функционировать в отсутствии связи с Интернет, а также в случае отказа сервера.
Цель — оградить пользователя от проблем соединения на его устройстве. Как было бы обидно не сохранить созданные в google docs таблицы из-за потери wi-fi в ближайшем фастфуде!
Для этого нужно инициализировать приложение в браузере и затем закэшировать ресурсы, минимально необходимые для функционирования. После этого приложение возможно запустить из кэша в случае недоступности сервера.
Решение задачи заключается в следующем:
Об этом уже есть замечательная статья [1], но с тех пор кое-что изменилось. Ранее была популярна технология ApplicationCache, о которой можно почитать здесь [2]. С ApplicationCache был ряд проблем, такой технологии не хватало гибкости и на данный момент она устарела и не рекомендуется к использованию.
Теперь на арену выходит CacheStorage, с которым можно работать в ServiceWorker’е.
ServiceWorker [3] — это новая технология, позволяющая запускать javascript-код в браузере в фоновом режиме — аналог сервисов (служб) в операционных системах. ServiceWorker запускается с web-ресурса и продолжает работать в браузере независимо от приложения, которое его инициализировало.
Часто цель применения ServiceWorker это получение push-уведомлений в браузере и контроль кэшируемых ресурсов, последнее как раз наш случай.
CacheStorage [4] представляет собой контейнер для хранения кэша сетевых ресурсов. Глобальный объект CacheStorage доступен по имени caches. Его составляющие это объекты типа Cache [5].
Cache — это именованное хранилище из пар: объект Request — объект Response. Для каждого закэшированного ресурса экземпляр Cache будет хранить request и response, созданные функцией fetch [6].
Теперь разберём всё это на небольшом тестовом приложении. Допустим, что файлы расположены на локальном сервере по адресу localhost/test_serviceworker [7]. Поставим задачи. Для того, чтобы управлять кэшированием, необходимо:
Чтобы запустить ServiceWorker, его для начала необходимо зарегистрировать. В наше приложение нужно добавить код следующего содержания:
workerLoader.js
// при регистрации указываем на js-файл с кодом serviceWorker’а
// получаем Promise объект
navigator.serviceWorker.register(
'/test_serviceworker/appCache.js'
).then(function(registration) {
// при удачной регистрации имеем объект типа ServiceWorkerRegistration
console.log('ServiceWorker registration', registration);
// строкой ниже можно прекратить работу serviceWorker’а
//registration.unregister();
}).catch(function(err) {
throw new Error('ServiceWorker error: ' + err);
});
Следующим шагом пишем простой код ServiceWorker’а.
appCache.js
self.addEventListener('install', function(event) {
// инсталляция
console.log('install', event);
});
self.addEventListener('activate', function(event) {
// активация
console.log('activate', event);
});
Важно иметь в виду, что serviceWorker никак не связан с глобальной областью видимости приложения, в котором был зарегистрирован, и вообще не имеет объекта window. В self у него находится объект типа ServiceWorkerGlobalScope, на котором устанавливаются обработчики событий.
После предпринятых действий и обновления страницы в chrome по адресу chrome://inspect/#service-workers можно увидеть примерно такую картину:
На данном скриншоте в браузере запущено в фоновом режиме три js-файла, первый из которых является кэширующим сервисом из данного примера.
Далее в коде serviceWorker’а на этапе инсталляции создаём первоначальный кэш из необходимых ресурсов.
appCache.js
// наименование для нашего хранилища кэша
var CACHE_NAME = 'app_serviceworker_v_1',
// ссылки на кэшируемые файлы
cacheUrls = [
'/test_serviceworker/',
'/test_serviceworker/index.html',
'/test_serviceworker/css/custom.css',
'/test_serviceworker/images/icon.png',
'/test_serviceworker/js/main.js'
];
self.addEventListener('install', function(event) {
// задержим обработку события
// если произойдёт ошибка, serviceWorker не установится
event.waitUntil(
// находим в глобальном хранилище Cache-объект с нашим именем
// если такого не существует, то он будет создан
caches.open(CACHE_NAME).then(function(cache) {
// загружаем в наш cache необходимые файлы
return cache.addAll(cacheUrls);
})
);
});
Обратите внимание, все ссылки указаны относительно корня. Также среди кэшируемых ресурсов нет файла workerLoader.js, который регистрирует serviceWorker. Его кэшировать не желательно, т.к. в режиме offline приложение и без него будет работать. Но если срочно будет необходимо отключить serviceWorker, могут возникнуть проблемы. Пользователи вынуждены будут ждать пока serviceWorker обновится самостоятельно (посредством сравнения содержимого).
Далее добавляем в код serviceWorker’а обработчик события fetch. И на запрос ресурса выдаём его из кэша.
appCache.js
self.addEventListener('fetch', function(event) {
event.respondWith(
// ищем запрашиваемый ресурс в хранилище кэша
caches.match(event.request).then(function(cachedResponse) {
// выдаём кэш, если он есть
if (cachedResponse) {
return cachedResponse;
}
// иначе запрашиваем из сети как обычно
return fetch(event.request);
})
);
});
Но не всегда всё так просто. Допустим наши файлы статики поменялись на сервере. Усложним наш код. Проверим дату последнего обновления ресурса вытащив параметр last-modified из HTTP заголовков. И при необходимости подгрузим свежую версию файла и обновим кэш.
appCache.js
// период обновления кэша - одни сутки
var MAX_AGE = 86400000;
self.addEventListener('fetch', function(event) {
event.respondWith(
// ищем запрошенный ресурс среди закэшированных
caches.match(event.request).then(function(cachedResponse) {
var lastModified, fetchRequest;
// если ресурс есть в кэше
if (cachedResponse) {
// получаем дату последнего обновления
lastModified = new Date(cachedResponse.headers.get('last-modified'));
// и если мы считаем ресурс устаревшим
if (lastModified && (Date.now() - lastModified.getTime()) > MAX_AGE) {
fetchRequest = event.request.clone();
// создаём новый запрос
return fetch(fetchRequest).then(function(response) {
// при неудаче всегда можно выдать ресурс из кэша
if (!response || response.status !== 200) {
return cachedResponse;
}
// обновляем кэш
caches.open(CACHE_NAME).then(function(cache) {
cache.put(event.request, response.clone());
});
// возвращаем свежий ресурс
return response;
}).catch(function() {
return cachedResponse;
});
}
return cachedResponse;
}
// запрашиваем из сети как обычно
return fetch(event.request);
})
);
});
Примечание, для повторного запроса используются клоны request и response. Только один раз возможно отправить request и только один раз можно прочитать response. Из-за этого ограничения мы создаём копии этих объектов.
Подобным образом можно контролировать процесс кэширования. Теперь заметно явное преимущество перед appcache manifest в его декларативном стиле. Для разработчика открыта возможность оперировать HTTP заголовками, статус-кодами, содержимым и реализовать любую логику для отдельно взятых ресурсов.
Тема web-приложений в режиме offline существует давно. Возможно ServiceWorker и CacheStorage лишь временное решение. Тем не менее, это уже имеет смысл использовать. Даже если данные технологии устареют, производители браузеров создадут что-нибудь в качестве замены. Стоит только следить за прогрессом в этом направлении!
Лучшими материалами для написания этой статьи были:
http://www.html5rocks.com/en/tutorials/service-worker/introduction/ [9],
https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers [3]. Несмотря на их наличие, решил поделиться информацией на русском языке.
Автор: dolgo
Источник [10]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/115016
Ссылки в тексте:
[1] статья: https://habrahabr.ru/post/117123/
[2] здесь: https://habrahabr.ru/post/151815/
[3] ServiceWorker: https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers
[4] CacheStorage: https://developer.mozilla.org/en-US/docs/Web/API/CacheStorage
[5] Cache: https://developer.mozilla.org/en-US/docs/Web/API/Cache
[6] fetch: https://learn.javascript.ru/fetch
[7] localhost/test_serviceworker: http://localhost/test_serviceworker/
[8] и это не мало: http://caniuse.com/#feat=serviceworkers
[9] http://www.html5rocks.com/en/tutorials/service-worker/introduction/: http://www.html5rocks.com/en/tutorials/service-worker/introduction/
[10] Источник: https://habrahabr.ru/post/279291/
Нажмите здесь для печати.