Фоновый режим синхронизации с помощью Service Workers

Фоновый режим синхронизации с помощью Service Workers

От автора: относительно Service Workers есть один момент. В марте 2018 года в iOS Safari начали работать Service Workers — таким образом на сегодня все основные браузеры поддерживают автономные параметры. И это важнее, чем когда-либо: 20% взрослых в Соединенных Штатах не имеют домашнего Интернета, поэтому эти люди полагаются для доступа к большей части информации только на мобильный телефон. Это может включать в себя что-то такое простое, как проверка баланса счета в банке, или что-то такое же утомительное, как поиск работы, или даже исследование болезней.

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

Жизненный цикл запроса

В настоящее время запрос может выглядеть так:

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

Фоновая синхронизация

Жизненный цикл с фоновой синхронизацией немного отличается. Сначала пользователь делает запрос, но вместо того, чтобы запрос был предпринят немедленно, вмешивается Service Workers. Он проверяет, есть ли у пользователя доступ в Интернет — если это так, отлично. Запрос будет отправлен. Если нет, Service Workers будет ждать, пока у пользователя не появится Интернет, и в этот момент отправит запрос после того, как он получит данные из IndexedDB. Лучше всего будет работать фоновая синхронизация, она отправляет запрос, даже если пользователь ушел с исходной страницы.

Хотя фоновая синхронизация полностью поддерживается только в Chrome, Firefox и Edge в настоящее время работают над ее реализацией. К счастью, с использованием опреления функций и onLine и offLine событий, можно смело использовать фоновую синхронизацию в любом приложении, включая резервный вариант.

(Если вы хотите следовать руководству, код можно найти здесь, а саму демо-версию — здесь.)

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

Когда вы впервые настраиваете Service Workers, вам нужно зарегистрировать его из файла JavaScript приложения. Это может выглядеть так:

if(navigator.serviceWorker) { navigator.serviceWorker.register('serviceworker.js');
}

Обратите внимание, что мы используем определение функций даже при регистрации Service Workers. Применение определения функций практически не имеет никаких недостатков, и это предотвратит возникновение ошибок в старых браузерах, таких как Internet Explorer 11, когда Service Workers недоступен. В целом, это полезная привычка всегда применять его, даже если в этом нет необходимости.

Когда мы настраиваем фоновую синхронизацию, функция регистрации изменяется и может выглядеть примерно так:

if(navigator.serviceWorker) { navigator.serviceWorker.register('./serviceworker.js') .then(function() { return navigator.serviceWorker.ready }) .then(function(registration) { document.getElementById('submitForm').addEventListener('click', (event) => { registration.sync.register('example-sync') .catch(function(err) { return err; }) }) }) .catch( /.../ ) }

Это намного больше кода, но сейчас мы его подробно разберем.

Сначала мы регистрируем Service Workers, как и раньше, но теперь мы пользуемся тем фактом, что функция register возвращает promise. Следующая часть — это navigator.serviceWorker.ready. Это свойство, доступное Service Workers только для чтения, оно, по сути, просто позволяет узнать, готов ли Service Workers или нет. Это свойство дает нам возможность отложить выполнение следующих функций до тех пор, пока Service Workers не будет фактически готов.

Далее у нас есть ссылка на регистрацию Service Workers. Мы поместим прослушиватель событий в кнопку отправки, и в этот момент зарегистрируем событие синхронизации и передадим строку. Эта строка будет позже использована на стороне Service Workers.

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

if(navigator.serviceWorker) { navigator.serviceWorker.register('./serviceworker.js') .then(function() { return navigator.serviceWorker.ready }) .then(function(registration) { document.getElementById('submitForm').addEventListener('click', (event) => { if(registration.sync) { registration.sync.register('example-sync') .catch(function(err) { return err; }) } }) }) }

Теперь давайте посмотрим на Service Workers.

self.onsync = function(event) { if(event.tag == 'example-sync') { event.waitUntil(sendToServer()); }
}

Мы прикрепляем функцию к прослушивателю событий для фоновой синхронизации onsync. Мы хотим следить за строкой, которую передали в функцию register. Мы отслеживаем эту строку, используя event.tag.

Мы также используем event.waitUntil. Поскольку Service Workers не работает постоянно — он «просыпается» для выполнения задачи, а затем «возвращается в спящий режим» — мы хотим использовать event.waitUntil, чтобы поддерживать его в активном состоянии. Эта функция принимает параметр функции. Функция, которую мы передаем, возвращает promise, и event.waitUntil будет удерживать Service Workers «активным», пока эта функция не будет разрешена. Если мы не используем event.waitUntil, запрос может никогда не дойти до сервера, потому что Service Workers запустит функцию onsync и сразу же вернется в спящий режим.

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

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

return new Promise(function(resolve, reject) { var db = indexedDB.open('newsletterSignup'); db.onsuccess = function(event) { this.result.transaction("newsletterObjStore").objectStore("newsletterObjStore").getAll().onsuccess = function(event) { resolve(event.target.result); } } db.onerror = function(err) { reject(err); }
});

Мы возвращаем promise и используем параметры resolve и reject, чтобы сделать эту функцию более соответствующей Service Workers.

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

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

fetch('https://www.mocky.io/v2/5c0452da3300005100d01d1f', { method: 'POST', body: JSON.stringify(response), headers:{ 'Content-Type': 'application/json' }
})

Конечно, все это будет работать, только если у пользователя есть доступ к Интернету. Если у пользователя нет доступа к Интернету, Service Workers будет ожидать, пока соединение не восстановится. Если после того, как соединение возобновится, запрос на выборку не удастся, Service Workers попытается повторить его не более трех раз, прежде чем прекратит попытки отправить запрос навсегда.

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

Поддержка устаревших браузеров

К сожалению, Service Workers не поддерживаются в устаревших браузерах, и на данный момент функция фоновой синхронизации поддерживается только в Chrome. В этой части мы рассмотрим использование других автономных функций, чтобы дублировать фоновую синхронизацию и предложить аналогичные возможности.

События Online и Offline

Мы начнем с событий online и offline. Код для регистрации Service Workers в прошлый раз выглядел так:

if(navigator.serviceWorker) { navigator.serviceWorker.register('./serviceworker.js') .then(function() { return navigator.serviceWorker.ready }) .then(function(registration) { document.getElementById('submitForm').addEventListener('click', (event) => { event.preventDefault(); saveData().then(function() { if(registration.sync) { registration.sync.register('example-sync') .catch(function(err) { return err; }) } }); }) })
}

Давайте кратко разберем этот код. После регистрации Service Workers мы используем promise, возвращаемый из navigator.serviceWorker.ready, чтобы убедиться, что Service Workers действительно готов к работе. Как только Service Workers будет готов к работе, мы прикрепляем к кнопке отправки прослушиватель событий и сразу сохраняем данные в IndexedDB. К счастью для нас, IndexedDB поддерживается практически во всех браузерах, поэтому мы вполне можем положиться на него.

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

if(registration.sync) { registration.sync.register('example-sync') .catch(function(err) { return err; })
} else { if(navigator.onLine) { sendData(); } else { alert("You are offline! When your internet returns, we'll finish up your request."); }
}

Дополнительная поддержка

Мы используем navigator.onLine для проверки интернет-соединения пользователя. Если подключение к Интернету есть, он вернет true. Если это так, мы отправим данные. В противном случае мы выведем предупреждение, сообщающее пользователю, что его данные не были отправлены.

Давайте добавим пару событий, чтобы отслеживать интернет-соединение. Сначала мы добавим событие, чтобы отслеживать режим оффлайн.

window.addEventListener('offline', function() { alert('You have lost internet access!');
});

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

window.addEventListener('online', function() { if(!navigator.serviceWorker && !window.SyncManager) { fetchData().then(function(response) { if(response.length > 0) { return sendData(); } }); }
});

Как только интернет-соединение восстановится, мы быстро проверим, доступен ли service worker, а также доступна ли синхронизация. Нам нужно проверить это, потому что, если в браузере доступна синхронизация, нам не нужно полагаться на резервный вариант — это приведет к двум извлечениям. Однако, если мы используем резервный вариант, мы сначала извлекаем данные из IndexedDB следующим образом:

var myDB = window.indexedDB.open('newsletterSignup'); myDB.onsuccess = function(event) { this.result.transaction("newsletterObjStore").objectStore("newsletterObjStore").getAll().onsuccess = function(event) { return event.target.result; };
}; myDB.onerror = function(err) { reject(err);
}

Далее мы проверим, что ответ от IndexedDB действительно содержит данные, и если это так, мы отправим их на сервер.

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

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

Следующие шаги

Браузеры Edge и Firefox в настоящее время работают над реализацией фоновой синхронизации, что очень здорово. Это одна из лучших функций для обеспечения более удобного взаимодействия с пользователями, у которых интернет-соединение может прерываться. К счастью, с помощью событий online и offline и IndexedDB, мы можем начать предоставлять пользователям лучший опыт уже сегодня.

Автор: Carmen Bourlon

Источник: https://davidwalsh.name/

Редакция: Команда webformyself.