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

Джейк Арчибальд
Jake Archibald

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

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

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

Функция фоновой загрузки доступна по умолчанию, начиная с Chrome 74.

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

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

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

  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
Общий размер тел ответов (после распаковки с помощью gzip).

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

Если количество фоновых загрузок превысит указанное здесь значение, загрузка будет прервана. Совершенно нормально, если загрузка меньше значения 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 кэша .

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

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

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');
  }
});

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

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