Przedstawiamy pobieranie w tle

Jake Archibald
Jake Archibald

W 2015 r. wprowadziliśmy synchronizację w tle, która umożliwia odroczenie pracy service workera do momentu, gdy użytkownik będzie mieć połączenie z internetem. Oznacza to, że użytkownik może wpisać wiadomość, kliknąć „Wyślij” i opuścić witrynę, wiedząc, że wiadomość zostanie wysłana od razu lub po uzyskaniu połączenia z internetem.

Jest to przydatna funkcja, ale wymaga, aby proces roboczy usługi był aktywny przez cały czas trwania pobierania. Nie stanowi to problemu w przypadku krótkich zadań, takich jak wysyłanie wiadomości, ale jeśli zadanie trwa zbyt długo, przeglądarka zakończy działanie service workera, ponieważ w przeciwnym razie stanowiłoby to zagrożenie dla prywatności użytkownika i baterii.

Co zrobić, jeśli chcesz pobrać coś, co może zająć dużo czasu, np. film, podcasty lub poziomy gry? Do tego służy pobieranie w tle.

Interfejs Background Fetch jest domyślnie dostępny od Chrome 74.

Oto 2-minutowa prezentacja pokazująca tradycyjny stan rzeczy w porównaniu z używaniem pobierania w tle:

Jak to działa

Pobieranie w tle działa w ten sposób:

  1. Informujesz przeglądarkę, aby wykonała grupę pobrań w tle.
  2. Przeglądarka pobiera te elementy i wyświetla użytkownikowi postęp.
  3. Gdy pobieranie się zakończy lub nie powiedzie, przeglądarka otworzy proces roboczy usługi i wyśle zdarzenie, aby poinformować Cię o tym, co się stało. W tym miejscu możesz zdecydować, co zrobić z odpowiedziami.

Jeśli po kroku 1 użytkownik zamknie strony Twojej witryny, nic się nie stanie – pobieranie będzie kontynuowane. Ponieważ pobieranie jest dobrze widoczne i można je łatwo przerwać, nie ma obaw o prywatność związanych ze zbyt długim zadaniem synchronizacji w tle. Ponieważ service worker nie działa nieustannie, nie ma obawy, że może nadużywać systemu, np. wydobywać bitcoiny w tle.

Na niektórych platformach (np. Androidzie) przeglądarka może się zamknąć po kroku 1, ponieważ może przekazać pobieranie danych systemowi operacyjnemu.

Jeśli użytkownik rozpocznie pobieranie w trybie offline lub przejdzie w tryb offline w trakcie pobierania, pobieranie w tle zostanie wstrzymane i wznowione później.

Interfejs API

Wykrywanie funkcji

Jak w przypadku każdej nowej funkcji, musisz sprawdzić, czy przeglądarka ją obsługuje. W przypadku pobierania w tle wystarczy:

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

Rozpoczynanie pobierania w tle

Główny interfejs API jest powiązany z rejestracją skryptu service worker, więc najpierw zarejestruj skrypt service worker. Następnie:

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

Funkcja backgroundFetch.fetch przyjmuje 3 argumenty:

Parametry
id string
jednoznacznie identyfikuje to pobieranie w tle.

backgroundFetch.fetch odrzuci żądanie, jeśli identyfikator pasuje do istniejącego pobierania w tle.

requests Array<Request|string>
Elementy do pobrania. Ciągi znaków będą traktowane jako adresy URL i przekształcane w Request za pomocą new Request(theString).

Możesz pobierać dane z innych punktów początkowych, o ile zasoby na to zezwalają za pomocą CORS.

Uwaga: Chrome nie obsługuje obecnie żądań, które wymagają wstępnego sprawdzenia CORS.

options Obiekt, który może zawierać te elementy:
options.title string
Tytuł, który przeglądarka ma wyświetlać wraz z postępem.
options.icons Array<IconDefinition>
Tablica obiektów z polami „src”, „size” i „type”.
options.downloadTotal number
Łączny rozmiar treści odpowiedzi (po rozpakowaniu).

Jest to opcjonalne, ale zdecydowanie zalecamy podanie tej informacji. Służy do informowania użytkownika o rozmiarze pobieranego pliku i postępach pobierania. Jeśli tego nie zrobisz, przeglądarka poinformuje użytkownika, że rozmiar jest nieznany, co może spowodować, że użytkownik zrezygnuje z pobierania.

Jeśli liczba pobrań w tle przekroczy podaną tutaj wartość, pobieranie zostanie przerwane. Jeśli pobieranie zajmuje mniej miejsca niż downloadTotal, nie ma problemu. Jeśli nie masz pewności, ile miejsca zajmie pobieranie, lepiej zachować ostrożność.

backgroundFetch.fetch zwraca obietnicę, która jest realizowana za pomocą obiektu BackgroundFetchRegistration. Szczegóły omówię później. Obietnica jest odrzucana, jeśli użytkownik zrezygnował z pobierania lub jeden z podanych parametrów jest nieprawidłowy.

Wysyłanie wielu żądań dotyczących jednego pobierania w tle umożliwia łączenie elementów, które logicznie stanowią dla użytkownika jedną całość. Na przykład film może być podzielony na tysiące zasobów (co jest typowe w przypadku MPEG-DASH) i zawierać dodatkowe zasoby, takie jak obrazy. Poziom gry może być rozłożony na wiele zasobów JavaScript, obrazów i dźwięków. Dla użytkownika to po prostu „film” lub „poziom”.

Pobieranie istniejącego pobrania w tle

Istniejące pobieranie w tle możesz uzyskać w ten sposób:

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

…przekazując identyfikator żądanego pobierania w tle. get zwraca undefined, jeśli nie ma aktywnego pobierania w tle o tym identyfikatorze.

Pobieranie w tle jest uznawane za „aktywne” od momentu zarejestrowania do momentu, gdy zakończy się powodzeniem, niepowodzeniem lub zostanie przerwane.

Listę wszystkich aktywnych pobrań w tle możesz uzyskać za pomocą tego polecenia: getIds

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

Rejestracje pobierania w tle

BackgroundFetchRegistration (bgFetch w przykładach powyżej) ma te elementy:

Właściwości
id string
Identyfikator pobierania w tle.
uploadTotal number
 Liczba bajtów do wysłania na serwer.
uploaded number
Liczba bajtów, które zostały wysłane.
downloadTotal number
Wartość podana podczas rejestracji pobierania w tle lub zero.
downloaded number
Liczba bajtów, które zostały odebrane.

Ta wartość może się zmniejszyć. Na przykład jeśli połączenie zostanie przerwane i pobieranie nie będzie mogło zostać wznowione, przeglądarka ponownie rozpocznie pobieranie tego zasobu od początku.

result

Jedna z tych wartości:

  • "" – pobieranie w tle jest aktywne, więc nie ma jeszcze wyników.
  • "success" – pobieranie w tle zostało zakończone.
  • "failure" – nie udało się pobrać danych w tle. Ta wartość pojawia się tylko wtedy, gdy pobieranie w tle całkowicie się nie powiedzie, np. gdy przeglądarka nie może ponowić próby ani wznowić pobierania.
failureReason

Jedna z tych wartości:

  • "" – pobieranie w tle nie zakończyło się niepowodzeniem.
  • "aborted" – użytkownik przerwał pobieranie w tle lub wywołano funkcję abort().
  • "bad-status" – jedna z odpowiedzi miała stan inny niż OK, np. 404.
  • "fetch-error" – jedno z pobrań nie powiodło się z innego powodu, np. z powodu CORS, MIX, nieprawidłowej częściowej odpowiedzi lub ogólnego błędu sieci w przypadku pobrania, którego nie można ponowić.
  • "quota-exceeded" – limit miejsca na dane został osiągnięty podczas pobierania w tle.
  • "download-total-exceeded" – podana wartość `downloadTotal` została przekroczona.
recordsAvailable boolean
Czy można uzyskać dostęp do podstawowych żądań i odpowiedzi?

Gdy ta wartość jest fałszywa, nie można używać match ani matchAll.

Metody
abort() Zwraca Promise<boolean>
Przerwij pobieranie w tle.

Zwrócony obiekt Promise zostanie rozwiązany z wartością „true”, jeśli pobieranie zostało pomyślnie przerwane.

matchAll(request, opts) Zwraca Promise<Array<BackgroundFetchRecord>>
Pobieranie żądań i odpowiedzi.

Argumenty są takie same jak w przypadku interfejsu API pamięci podręcznej. Wywołanie bez argumentów zwraca obietnicę wszystkich rekordów.

Więcej informacji znajdziesz poniżej.

match(request, opts) Zwraca Promise<BackgroundFetchRecord>
jak wyżej, ale zwraca pierwsze dopasowanie.
Wydarzenia
progress Wywoływane, gdy zmieni się dowolna z wartości uploaded, downloaded, result lub failureReason.

Śledzenie postępów

Możesz to zrobić za pomocą zdarzenia progress. Pamiętaj, że downloadTotal to podana przez Ciebie wartość lub 0, jeśli nie podasz żadnej wartości.

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}%`);
});

Otrzymywanie żądań i odpowiedzi

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 to BackgroundFetchRecord, który wygląda tak:

Właściwości
request Request
Przesłana prośba.
responseReady Promise<Response>
Pobrana odpowiedź.

Odpowiedź jest w stanie oczekiwania, ponieważ mogła jeszcze nie zostać odebrana. Obietnica zostanie odrzucona, jeśli pobieranie się nie powiedzie.

Zdarzenia skryptu service worker

Wydarzenia
backgroundfetchsuccess Wszystkie dane zostały pobrane.
backgroundfetchfailure Co najmniej jedno pobranie nie powiodło się.
backgroundfetchabort Co najmniej jedno pobieranie nie powiodło się.

Jest to przydatne tylko wtedy, gdy chcesz wyczyścić powiązane dane.

backgroundfetchclick Użytkownik kliknął interfejs postępu pobierania.

Obiekty zdarzeń mają te elementy:

Właściwości
registration BackgroundFetchRegistration
Metody
updateUI({ title, icons }) Umożliwia zmianę tytułu lub ikon, które zostały ustawione na początku. Jest to opcjonalne, ale w razie potrzeby możesz podać więcej informacji. Możesz to zrobić *tylko raz* podczas wydarzeń backgroundfetchsuccessbackgroundfetchfailure.

Reagowanie na powodzenie lub niepowodzenie

Widzieliśmy już zdarzenie progress, ale jest ono przydatne tylko wtedy, gdy użytkownik ma otwartą stronę Twojej witryny. Główną zaletą pobierania w tle jest to, że wszystko działa nadal po opuszczeniu strony przez użytkownika lub nawet po zamknięciu przeglądarki.

Jeśli pobieranie w tle zakończy się pomyślnie, Twój service worker otrzyma zdarzenie backgroundfetchsuccess, a event.registration będzie rejestracją pobierania w tle.

Po tym zdarzeniu pobrane żądania i odpowiedzi nie będą już dostępne, więc jeśli chcesz je zachować, przenieś je np. do interfejsu Cache API.

Podobnie jak w przypadku większości zdarzeń service workerów, użyj event.waitUntil, aby service worker wiedział, kiedy zdarzenie się zakończy.

Na przykład w usłudze 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!' });
  }());
});

Przyczyną niepowodzenia może być pojedynczy błąd 404, który nie był dla Ciebie ważny, więc nadal warto skopiować niektóre odpowiedzi do pamięci podręcznej, jak opisano powyżej.

Reagowanie na kliknięcie

Interfejs wyświetlający postęp i wynik pobierania jest klikalny. Zdarzenie backgroundfetchclick w usłudze Service Worker umożliwia reagowanie na to zdarzenie. Jak wyżej, event.registration będzie rejestracją pobierania w tle.

Najczęstszym działaniem związanym z tym zdarzeniem jest otwarcie okna:

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

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

Dodatkowe materiały

Korekta: w poprzedniej wersji tego artykułu funkcja pobierania w tle została błędnie określona jako „standard internetowy”. Interfejs API nie jest obecnie zgodny ze standardami. Specyfikację można znaleźć w WICG jako projekt raportu grupy społeczności.