Представляем фоновую выборку

В 2015 году мы представили фоновую синхронизацию , которая позволяет сервисному работнику откладывать работу до тех пор, пока у пользователя не будет подключения. Это означает, что пользователь может ввести сообщение, нажать «Отправить» и покинуть сайт, зная, что сообщение будет отправлено либо сейчас, либо когда у него будет подключение.

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

Итак, что, если вам нужно загрузить что-то, что может занять много времени, например фильм, подкасты или уровни игры. Для этого и нужна фоновая выборка .

Фоновая выборка доступна по умолчанию, начиная с Chrome 74.

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

Попробуйте демо-версию самостоятельно и просмотрите код .

Как это работает

Фоновая выборка работает следующим образом:

  1. Вы указываете браузеру выполнить группу выборок в фоновом режиме.
  2. Браузер извлекает эти данные, отображая прогресс пользователю.
  3. После завершения или сбоя выборки браузер открывает вашего сервис-воркера и запускает событие, сообщающее вам, что произошло. Здесь вы решаете, что делать с ответами, если что.

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

На некоторых платформах (например, Android) браузер может закрыться после шага 1, поскольку браузер может передать выборку операционной системе.

Если пользователь начинает загрузку в автономном режиме или отключается во время загрузки, фоновая загрузка будет приостановлена ​​и возобновлена ​​позже.

API

Обнаружение функции

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

if ('BackgroundFetchManager' in self) {
  // This browser supports Background Fetch!
}

Запускаем фоновую выборку

Основной API зависает от регистрации сервис-воркера , поэтому сначала убедитесь, что вы зарегистрировали сервис-воркера. Затем:

navigator.serviceWorker.ready.then(async (swReg) => {
  const bgFetch = await swReg.backgroundFetch.fetch('my-fetch', ['/ep-5.mp3', 'ep-5-artwork.jpg'], {
    title: 'Episode 5: Interesting things.',
    icons: [{
      sizes: '300x300',
      src: '/ep-5-icon.png',
      type: 'image/png',
    }],
    downloadTotal: 60 * 1024 * 1024,
  });
});

backgroundFetch.fetch принимает три аргумента:

Параметры
id string
однозначно идентифицирует эту фоновую выборку.

backgroundFetch.fetch отклонит, если идентификатор соответствует существующей фоновой выборке.

requests Array< Request |string>
Вещи, которые нужно принести. Строки будут обрабатываться как URL-адреса и превращаться в Request с помощью new Request(theString) .

Вы можете получать данные из других источников, если ресурсы позволяют это через CORS .

Примечание. Chrome в настоящее время не поддерживает запросы, требующие предварительной проверки CORS.

options Объект, который может включать в себя следующее:
options.title string
Заголовок, который браузер будет отображать вместе с прогрессом.
options.icons Array< IconDefinition >
Массив объектов с `src`, `size` и `type`.
options.downloadTotal number
Общий размер тел ответа (после распаковки).

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

Если фоновая загрузка превысит указанное здесь число, она будет прервана. Совершенно нормально, если загрузка меньше, чем downloadTotal , поэтому, если вы не уверены, какова будет общая сумма загрузок, лучше проявить осторожность.

backgroundFetch.fetch возвращает обещание, которое разрешается с помощью BackgroundFetchRegistration . Подробности я расскажу позже. Обещание отклоняется, если пользователь отказался от загрузок или один из предоставленных параметров недействителен.

Предоставление множества запросов для одной фоновой выборки позволяет объединить вещи, которые для пользователя логически являются единым целым. Например, фильм может быть разделен на тысячи ресурсов (типично для MPEG-DASH ) и содержать дополнительные ресурсы, такие как изображения. Уровень игры может быть распределен по множеству ресурсов JavaScript, изображений и аудио. Но для пользователя это просто «фильм» или «уровень».

Получение существующей фоновой выборки

Вы можете получить существующую фоновую выборку следующим образом:

navigator.serviceWorker.ready.then(async (swReg) => {
  const bgFetch = await swReg.backgroundFetch.get('my-fetch');
});

…передавая идентификатор нужной фоновой выборки. get возвращает undefined если нет активной фоновой выборки с этим идентификатором.

Фоновая выборка считается «активной» с момента ее регистрации до тех пор, пока она не завершится успешно, не завершится неудачей или не будет прервана.

Вы можете получить список всех активных фоновых выборок, используя getIds :

navigator.serviceWorker.ready.then(async (swReg) => {
  const ids = await swReg.backgroundFetch.getIds();
});

Регистрация фоновой выборки

BackgroundFetchRegistration ( bgFetch в приведенных выше примерах) имеет следующее:

Характеристики
id string
Идентификатор фоновой выборки.
uploadTotal number
Количество байт, которые будут отправлены на сервер.
uploaded number
Количество успешно отправленных байт.
downloadTotal number
Значение, предоставленное при регистрации фоновой выборки, или нулевое.
downloaded number
Количество успешно полученных байтов.

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

result

Одно из следующих:

  • "" — фоновая выборка активна, поэтому результата пока нет.
  • "success" — фоновая выборка прошла успешно.
  • "failure" — фоновая выборка не удалась. Это значение появляется только в случае полного сбоя фоновой выборки, поскольку браузер не может повторить попытку или возобновить ее.
failureReason

Одно из следующих:

  • "" — фоновая выборка не удалась.
  • "aborted" — фоновая выборка была прервана пользователем или была вызвана abort() .
  • "bad-status" — один из ответов имел неудовлетворительный статус, например 404.
  • "fetch-error" — одна из выборок не удалась по какой-либо другой причине, например, CORS, MIX, недопустимый частичный ответ или общий сбой сети для выборки, которую невозможно повторить.
  • "quota-exceeded" — во время фоновой выборки была достигнута квота хранилища.
  • "download-total-exceeded" — превышено указанное значение «downloadTotal».
recordsAvailable boolean
Можно ли получить доступ к основным запросам/ответам?

Если это ложное match , то matchAll использовать нельзя.

Методы
abort() Возвращает Promise<boolean>
Прервите фоновую выборку.

Возвращенное обещание разрешается с true, если выборка была успешно прервана.

matchAll(request, opts) Возвращает Promise<Array<BackgroundFetchRecord>>
Получите запросы и ответы.

Аргументы здесь такие же, как и у API кэша . Вызов без аргументов возвращает обещание для всех записей.

Более подробную информацию смотрите ниже.

match(request, opts) Возвращает Promise<BackgroundFetchRecord>
То же, что и выше, но разрешается при первом совпадении.
События
progress Вызывается при изменении любого из uploaded , downloaded , result или failureReason .

Отслеживание прогресса

Это можно сделать с помощью события progress . Помните, что downloadTotal — это любое указанное вами значение или 0 , если вы не указали значение.

bgFetch.addEventListener('progress', () => {
  // If we didn't provide a total, we can't provide a %.
  if (!bgFetch.downloadTotal) return;

  const percent = Math.round(bgFetch.downloaded / bgFetch.downloadTotal * 100);
  console.log(`Download progress: ${percent}%`);
});

Получение запросов и ответов

bgFetch.match('/ep-5.mp3').then(async (record) => {
  if (!record) {
    console.log('No record found');
    return;
  }

  console.log(`Here's the request`, record.request);
  const response = await record.responseReady;
  console.log(`And here's the response`, response);
});

record представляет собой BackgroundFetchRecord и выглядит следующим образом:

Характеристики
request Request
Запрос, который был предоставлен.
responseReady Promise<Response>
Надуманный ответ.

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

События для сервисных работников

События
backgroundfetchsuccess Все было успешно получено.
backgroundfetchfailure Не удалось выполнить одну или несколько операций выборки.
backgroundfetchabort Не удалось выполнить одну или несколько выборок.

Это действительно полезно, только если вы хотите выполнить очистку связанных данных.

backgroundfetchclick Пользователь нажал на пользовательский интерфейс процесса загрузки.

Объекты событий имеют следующее:

Характеристики
registration BackgroundFetchRegistration
Методы
updateUI({ title, icons }) Позволяет изменить заголовок/значки, которые вы изначально установили. Это необязательно, но при необходимости позволяет предоставить больше контекста. Вы можете сделать это только один раз во время событий backgroundfetchsuccess и backgroundfetchfailure .

Реакция на успех/неуспех

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

Если фоновая выборка успешно завершится, ваш сервис-воркер получит событие backgroundfetchsuccess , а event.registration будет регистрацией фоновой выборки.

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

Как и в случае с большинством событий сервис-воркера, используйте event.waitUntil , чтобы сервис-воркер знал, когда событие завершится.

Например, в вашем сервисном работнике:

addEventListener('backgroundfetchsuccess', (event) => {
  const bgFetch = event.registration;

  event.waitUntil(async function() {
    // Create/open a cache.
    const cache = await caches.open('downloads');
    // Get all the records.
    const records = await bgFetch.matchAll();
    // Copy each request/response across.
    const promises = records.map(async (record) => {
      const response = await record.responseReady;
      await cache.put(record.request, response);
    });

    // Wait for the copying to complete.
    await Promise.all(promises);

    // Update the progress notification.
    event.updateUI({ title: 'Episode 5 ready to listen!' });
  }());
});

Сбой мог сводиться к одной ошибке 404, которая, возможно, не имела для вас значения, поэтому, возможно, все же стоит скопировать некоторые ответы в кеш, как указано выше.

Реакция на клик

Пользовательский интерфейс, отображающий ход загрузки и результат, является кликабельным. Событие backgroundfetchclick в сервисном работнике позволяет вам отреагировать на это. Как указано выше, event.registration будет регистрацией фоновой выборки.

Обычное действие в этом событии — открыть окно:

addEventListener('backgroundfetchclick', (event) => {
  const bgFetch = event.registration;

  if (bgFetch.result === 'success') {
    clients.openWindow('/latest-podcasts');
  } else {
    clients.openWindow('/download-progress');
  }
});

Дополнительные ресурсы

Исправление: в предыдущей версии этой статьи фоновая выборка ошибочно называлась «веб-стандартом». API в настоящее время не находится на стадии разработки стандартов, спецификацию можно найти в WICG в виде проекта отчета группы сообщества.