В этой статье я хотел бы поговорить о Service Workers (SW).
Программное обеспечение позволяет нам подготовить наше приложение к работе в автономном режиме, чтобы оно работало, даже если у нас нет подключения к Интернету.
Они также позволяют нам использовать множество других дополнительных функций, таких как push-уведомления или фоновая синхронизация.
ПО продолжает работать даже после закрытия браузера, то есть Service Workers продолжают работать.
Это фоновый процесс.
Итак, давайте зарегистрируем нашего первого Service Worker. (В этой статье я реализую функциональные возможности, связанные с ПО, на простом JS, поскольку код написан на простом JS, и мы можем интегрировать его в любые JS-фреймворки, такие как Angular, React или Vue) В качестве первого шага добавим файл sw.js в корневую папку проекта.
В app.js нам нужно проверить, доступно ли ПО в навигаторе, то есть поддерживается ли ПО данным браузером.
Теперь, когда мы знаем, что ПО доступно, мы можем выполнить метод navigator.serviceWorker.register(), указав путь к файлу, в котором находится наше ПО, для его регистрации.
Этот метод фактически возвращает обещание.
Итак, чтобы получить информацию, как только это будет сделано, мы сможем присоединиться к ней.
Поскольку мы зарегистрировали наше первое ПО, давайте добавим первый прослушиватель событий.if ('serviceWorker' in navigator) { navigator.serviceWorker .
register('/sw.js') .
then(event => { console.log('Service worker registered', event); }); }
Как я уже говорил, ПО работает в фоновом режиме.
Но я не упомянул одну вещь: все они связаны с обработкой событий.
Чтобы подключить прослушиватели событий к SW, нам сначала нужно получить к нему доступ с помощью ключевого слова self, что по сути означает «предоставить мне доступ к SW», а затем мы можем выполнить метод addEventListener().
Программным обеспечением предоставляется доступ к специальному набору событий, например событию установки, которое запускается, когда браузер устанавливает Service Worker. Здесь мы выполняем функцию и получаем объект события, который автоматически передается функции браузером, и этот объект предоставляет нам информацию о событии установки.
Как мы видим, наш Service Worker успешно установлен.
self.addEventListener('install', event => {
console.log('Installing [Service Worker]', event);
});
Теперь мы можем приступить к реализации статического или предварительного кэширования.
Фаза установки Service Worker — отличное место для кэширования некоторых ресурсов, которые не меняются очень часто, например оболочки приложения или базового стиля.
В прослушивателе событий установки мы пишем кеши для доступа к API кеша, а затем метод open(), передающий имя нашего кеша.
Итак, мы открываем там новый тайник.
Но сначала нам нужно обернуть это выражение выражением event.waitUntil().
Он просто ждет, пока вся работа по кэшированию не будет завершена.
Это помешает завершению установки.
В блоке then мы получаем доступ к этому кешу и теперь можем добавлять в него контент. Когда мы пишем кэш.
addAll(), мы добавляем файлы, представляющие оболочку нашего приложения.
self.addEventListener('install', event => {
console.log('Installing [Service Worker]', event);
event.waitUntil(
caches.open('static')
.
then(cache => {
console.log('[Service Worker] Precaching App Shell');
cache.addAll([
'/',
'/index.html',
'/favicon.ico',
'/src/js/app.js',
'/src/js/chart.js',
'/src/js/materialize.js',
'/src/js/materialize.min.js',
'/src/css/materialize.css',
'/src/css/materialize.min.css',
'/src/css/style.css',
' https://fonts.googleapis.com/iconЭfamily=Material+Icons ',
' https://code.jquery.com/jquery-2.1.1.min.js ',
' https://cdn.jsdelivr.net/npm/[email protected] '
]);
}));
});
Теперь посмотрим, действительно ли эти файлы загружены в кеш.
Следующим шагом будет получение этих файлов из кеша, чтобы наше приложение могло работать в автономном режиме.
Давайте посмотрим, как это сделать.
Еще одно очень важное событие, которое мы также можем прослушивать, — это событие выборки.
Fetch будет запускаться всякий раз, когда наше веб-приложение извлекает что-либо, например файлы css и js или даже запросы xhr. Итак, в прослушивателе событий fetch Service Worker давайте удостоверимся, что мы действительно извлекаем данные из нашего кэша.
Сначала я добавлю это решение, а затем объясню, как оно работает.
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.
then(response => {
if (response) {
return response;
} else {
return fetch(event.request);
}
})
);
});
Выражение event.respondWith() позволяет нам перезаписывать отправляемые обратно данные.
Вы можете думать о Service Worker как о сетевом прокси, по крайней мере, если мы используем здесь событие fetch. Таким образом, каждый исходящий запрос на выборку проходит через Service Worker, как и каждый ответ. То есть, если мы ничего не делаем, ответ просто не передается.
Выражение Cashes.match() позволяет нам проверить, кэширован ли уже данный запрос.
Если да, он вернет кэшированный ответ. Мы не делаем сетевой запрос, мы перехватываем запрос и не выдаем новый, вместо этого мы просто смотрим то, что мы хотели запросить, и смотрим, есть ли это в кеше и если есть, то возвращаем обратно.
С другой стороны, если мы не находим его в кеше, мы хотим вернуть запрос на выборку туда, где мы к нему обращаемся, или туда, где мы просто продолжаем исходный запрос, поэтому мы обращаемся к fetch(event.request).
После всех этих изменений мы наконец можем использовать наше веб-приложение в автономном режиме.
Как видите, наше веб-приложение содержит диаграмму с некоторыми статическими данными, и когда мы нажимаем кнопку «ПОЛУЧИТЬ ДАННЫЕ», ничего не происходит. Теперь я хочу сделать так, чтобы при нажатии на эту кнопку мы получали некоторую статистику, отображали ее в виде диаграммы и сохраняли эти данные в кеше.
Таким образом мы реализуем динамическое кэширование.
Итак, давайте начнем.
Допустим, у нас есть конечная точка, которая возвращает статистику о том, сколько пользователей посетило наш сайт. Итак, теперь нам нужно взять эти данные и отобразить их на графике.
Object.keys(pureData).
forEach(key => tmp[sorter[key.toLowerCase()]] = { key, value: pureData[key] }); tmp.forEach(obj => orderedData[obj.key] = obj.value); const ctx = document.getElementById('myChart').
getContext('2d'); new Chart(ctx, { type: 'line', data: { labels: Object.entries(orderedData).
map(([key, _]) => key), datasets: [{ label: 'Users', backgroundColor: '#26a69a', borderColor: '#26a69a', fill: false, data: Object.entries(orderedData).
map(([_, value]) => value),
}]
}
});
});
};
Как и раньше, я добавлю решение, а затем объясню, как оно работает.
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.
then(response => { if (response) { return response; } else { return fetch(event.request) .
then(res => { return caches.open('dynamic') .
then(cache => {
cache.put(event.request.url, res.clone());
return res;
})
});
}
})
);
});
Динамическое кеширование просто означает, что у нас в любом случае есть событие выборки, и мы хотим сохранить возвращаемый ответ в нашем кеше.
Как и раньше, мы пишем кеши для доступа к API кеша и методу open(), передавая имя нашего кеша.
Оператор кэша.
put() просто сохраняет имеющиеся у нас данные.
Первый аргумент, который вы передаете, — это URL-адрес запроса события, идентификатор.
Второй аргумент – это ответ. Поэтому мы сохраняем именно тот клон, который нам нужен, он содержит все данные ответа, но мы возвращаем исходный ответ. Вот и все.
Сначала мы берем статистические данные с нашего сервера и сохраняем их в кеше.
Второй раз мы возьмем эти данные из кеша.
Это решение отлично работает не только с запросами xhr. Например, таким образом мы можем динамически кэшировать CSS-файлы или даже изображения.
Кроме того, хочу сказать несколько слов о фоновой синхронизации.
Фоновая синхронизация — это отправка данных на сервер, когда у нас нет подключения к Интернету.
Так как же это работает за кулисами? Мы можем использовать ПО для регистрации задачи синхронизации.
Конечно, регистрация задачи — это не все, что нам нужно сделать, нам также необходимо хранить данные, которые мы хотим отправить с запросом, в indexedDB. Так что если у нас не было соединения и оно было восстановлено, то SW сразу выполнит эту задачу.
Так называемое событие синхронизации будет выполнено в ПО, и мы сможем прослушивать это событие.
Приятно то, что это будет работать, даже если мы закроем вкладку или даже в браузерах на мобильных телефонах.
Теперь я хочу зарегистрировать первую задачу синхронизации и для этого сначала проверю, есть ли у нас доступ к Service Worker в данном браузере.
Однако нам также следует проверить, доступен ли в окне менеджер синхронизации.
Менеджер синхронизации — это, по сути, API, с помощью которого мы используем функции фоновой синхронизации.
Затем я перейду к своему Service Worker и смогу вызвать свойство Ready, чтобы убедиться, что оно настроено.
Итак, теперь мы можем с этим работать.
Чтобы зарегистрировать новую задачу синхронизации, мы должны получить доступ к свойству синхронизации (это дает нам доступ к менеджеру синхронизации), и там мы можем вызвать метод регистрации.
Он принимает только один аргумент — идентификатор, идентифицирующий эту задачу синхронизации.
Поэтому я назову это «запрос синхронизации».
Позже мы будем использовать это имя в Service Worker, чтобы реагировать на восстановленное соединение и проверять, какие у нас есть невыполненные задачи, а затем мы сможем использовать тег, чтобы узнать, что нам нужно делать с этой задачей.
if ('serviceWorker' in navigator && 'SyncManager' in window) {
navigator.serviceWorker.ready
.
then(sw => {
sw.sync.register('sync-request')
});
}
Теперь я хочу реализовать, чтобы после нажатия кнопки «POST DATA» данные, которые мы хотим отправить, сохранялись в indexedDB и регистрировали новую задачу синхронизации.
Для этого нам сначала нужно добавить в наш проект несколько дополнительных файлов, чтобы можно было легко работать с indexedDB. Далее давайте создадим данные, которые мы хотим сохранить.
Это будет простой объект с двумя свойствами.
Первое свойство — это идентификатор.
Второе свойство называется «воскресенье», значение которого равно 10 (для полноты картины :)).
Для хранения этих данных мы используем вспомогательную функцию writeData из Utilities.js, которая принимает два аргумента.
Первый аргумент — это имя базы данных, в которой будут храниться данные, а второй — сами наши данные.
После успешного завершения зарегистрируйте новую задачу синхронизации.
const syncButton = document.getElementById('sync-button');
syncButton.addEventListener('click', _ => {
if ('serviceWorker' in navigator && 'SyncManager' in window) {
navigator.serviceWorker.ready
.
then(sw => { const data = { id: new Date().
getTime(), sunday: 10 }; writeData('sync-requests', data) .
then(_ => {
sw.sync.register('sync-request')
});
});
}
});
И наконец, мы подошли к самому интересному — прослушиванию события синхронизации и реагированию на восстановление соединения.
Как и раньше, я добавлю решение и объясню, как оно работает.
self.addEventListener('sync', event => {
console.log('[Service Worker] Syncing');
if (event.tag === 'sync-request') {
event.waitUntil(
readAllData('sync-requests')
.
then(async data => { const requests = []; for (const d of data) { requests.push(fetch(' https://simple-pwa-8a005.firebaseio.com/data.json ', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: JSON.stringify({ sunday: d.sunday }) })); } const results = await Promise.all(requests); results.map((response, index) => { if (response.ok) { deleteItemFromData('sync-requests', data[index].
id);
}
})
})
);
}
});
Сначала нам нужно проверить тег события.
Затем я использую event.waitUntil(), как и раньше, это просто позволяет мне убедиться, что событие не завершится преждевременно.
Затем мы извлекаем данные, хранящиеся в indexedDB (используя вспомогательную функцию из Utilities.js), перебираем их, отправляем запрос на публикацию для каждого из фрагментов хранимых данных, а затем удаляем их из indexedDB, если мы успешно опубликовали это на сервер.
Вот и все.
Давайте попробуем это сейчас.
Чтобы протестировать эту функцию, мы должны выйти в автономный режим в нашем браузере, нажать кнопку «ОТПРАВИТЬ ДАННЫЕ», а затем снова подключиться к Интернету.
После нажатия кнопки «POST DATA», когда мы офлайн, ничего не происходит, но при восстановлении соединения мы видим, что синхронизация завершена.
И чтобы подтвердить, что данные действительно были отправлены на сервер, нам сначала нужно удалить наш запрос get из динамического кеша и нажать кнопку «ПОЛУЧИТЬ ДАННЫЕ».
Это все на данный момент. Увидимся позже.
Мой код доступен на github: https://github.com/Draakan/simplePWA Теги: #Разработка для Интернета вещей #программирование #frontend #js #ServiceWorkers
-
Подводные Камни — Анимация Травы (Unity3D)
19 Oct, 24