Przedstawiamy pobieranie w tle

Jake Archibald
Jake Archibald

W 2015 roku wprowadziliśmy synchronizację w tle, która umożliwia skryptowi service worker odłożenie zadania do momentu uzyskania połączenia przez użytkownika. Oznacza to, że użytkownik może wpisać wiadomość, kliknąć Wyślij i opuścić stronę, wiedząc, że wiadomość zostanie wysłana teraz lub po nawiązaniu połączenia.

Jest to przydatna funkcja, ale wymaga, aby skrypt service worker był aktywny przez cały czas pobierania danych. Nie stanowi to problemu w przypadku krótkich zadań, takich jak wysłanie wiadomości, ale jeśli zadanie trwa zbyt długo, przeglądarka wyłącza mechanizm Service Worker, ponieważ w przeciwnym razie istnieje ryzyko dla prywatności i baterii użytkownika.

A co w sytuacji, gdy chcesz pobrać coś, co może zająć dużo czasu, np. film, podcasty czy poziomy gry? Właśnie do tego służy funkcja pobierania w tle.

Pobieranie w tle jest dostępne domyślnie od Chrome 74.

Oto krótka dwuminutowa prezentacja przedstawiająca tradycyjny stan rzeczy w porównaniu z korzystaniem z pobierania w tle:

Wypróbuj wersję demonstracyjną i przejrzyj kod.

Jak to działa

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

  1. Kazasz przeglądarce wykonać grupę pobrań w tle.
  2. Przeglądarka pobiera te dane i wyświetla je użytkownikowi.
  3. Gdy pobieranie się zakończy lub nie uda się go pobrać, przeglądarka otworzy skrypt service worker i uruchomi zdarzenie, aby poinformować Cię, co się stało. Na tym etapie decydujesz, co zrobić z odpowiedziami (jeśli tak jest).

Jeśli po wykonaniu kroku 1 użytkownik zamknie strony witryny, pobieranie będzie kontynuowane. Ponieważ pobieranie jest bardzo widoczne i łatwe do przerwania, nie ma obaw o ochronę prywatności w związku ze zbyt długim zadaniem synchronizacji w tle. Skrypt service worker nie działa stale, dlatego nie ma obaw, że może nadużywać systemu, np. wydobywać bitcoiny w tle.

Na niektórych platformach (np. w Androidzie) możliwe jest zamknięcie przeglądarki po wykonaniu kroku 1, ponieważ przeglądarka może przekazać pobieranie do systemu operacyjnego.

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

Interfejs API

Wykrywanie cech

Tak jak w przypadku każdej nowej funkcji, warto sprawdzić, czy przeglądarka ją obsługuje. Aby pobrać w tle, wystarczy:

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

Rozpoczynam pobieranie w tle

Główny interfejs API jest zawieszony przy rejestracji skryptu service worker, dlatego najpierw zarejestruj taki skrypt. 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 pobieranie w tle.

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

requests Array<Request|string>
Elementy do pobrania. Ciągi tekstowe będą traktowane jako adresy URL i zamieniane w elementy typu Request za pomocą new Request(theString).

Możesz pobierać elementy z innych źródeł, o ile pozwalają na to zasoby przez CORS.

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

options Obiekt, który może obejmować:
options.title string
Tytuł przeglądarki, który wyświetla się wraz z postępem.
options.icons Array<IconDefinition>
Tablica obiektów o wartościach „src”, „size” i „type”.
options.downloadTotal number
Łączny rozmiar treści odpowiedzi (po wyjęciu z pliku gzip).

Chociaż jest to opcjonalne, zdecydowanie zalecamy jego podanie. Informuje on użytkownika o rozmiarze pobieranego pliku i informuje o postępach. Jeśli tego nie zrobisz, przeglądarka poinformuje użytkownika, że rozmiar jest nieznany, co może spowodować przerwanie pobierania.

Jeśli pobieranie w tle przekroczy liczbę podaną w tym miejscu, zostanie przerwane. Nie ma problemu, jeśli pobierany plik jest mniejszy niż downloadTotal, więc jeśli nie masz pewności, ile wyniesie łączna liczba pobrań, lepiej zachowaj ostrożność.

backgroundFetch.fetch zwraca obietnicę, która kończy się wartością BackgroundFetchRegistration. Później podam szczegóły. Obietnica zostanie odrzucona, jeśli użytkownik zrezygnował z pobierania lub jeden z podanych parametrów jest nieprawidłowy.

Udostępnienie wielu żądań w ramach jednego pobierania w tle umożliwia łączenie w sobie różnych elementów, które logicznie są traktowane jako jedna rzecz dla użytkownika. Na przykład film można podzielić na tysiące zasobów (typowych w przypadku MPEG-DASH) i udostępnić im dodatkowe zasoby, takie jak obrazy. Poziom gry może obejmować wiele zasobów JavaScript, graficznych i audio. Dla użytkownika jest to jednak po prostu „film” albo „poziom”.

Pobieram dotychczasowe pobieranie w tle

Pobieranie w tle może wyglądać tak:

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

...przesyłając parametr id żądanego pobierania tła. get zwraca wartość undefined, jeśli nie ma aktywnego pobierania w tle o tym identyfikatorze.

Pobieranie w tle jest uznawane za „aktywne” od momentu zarejestrowania, aż do momentu powodzenia, niepowodzenia lub przerwania.

Listę wszystkich aktywnych pobrań w tle możesz wyświetlić, korzystając z getIds:

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

Rejestracje pobierania w tle

Element BackgroundFetchRegistration (bgFetch w powyższych przykładach) zawiera:

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

Ta wartość może się zmniejszyć. Jeśli na przykład połączenie zostanie przerwane i nie można wznowić pobierania, w takim przypadku przeglądarka ponownie uruchomi pobieranie tego zasobu od zera.

result

Jedna z tych wersji:

  • "" – pobieranie w tle jest aktywne, więc nie ma jeszcze żadnych wyników.
  • "success" – udało się pobrać dane w tle.
  • "failure" – nie udało się pobrać w tle. Ta wartość pojawia się tylko wtedy, gdy pobieranie w tle nie powiedzie się, ponieważ przeglądarka nie może ponowić ani wznowić pobierania.
failureReason

Jedna z tych wersji:

  • "" – nie udało się pobrać w tle.
  • "aborted" – pobieranie w tle zostało przerwane przez użytkownika lub wywołano funkcję abort().
  • "bad-status" – jedna z odpowiedzi miała stan „niepoprawna” (np. 404).
  • "fetch-error" – jedno z pobierania nie powiodło się z innego powodu, np. CORS, MIX, nieprawidłowej odpowiedzi częściowej lub ogólnego błędu sieci w przypadku pobierania, 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 powiązanych z nimi żądań i odpowiedzi?

Jeśli ma wartość fałsz, nie można używać match i matchAll.

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

Zwrócona obietnica znika z wartością true, jeśli pobieranie zostało przerwane.

matchAll(request, opts) Zwraca Promise<Array<BackgroundFetchRecord>>
Otrzymuj żądania i odpowiedzi.

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

Więcej informacji znajdziesz poniżej.

match(request, opts) Zwraca Promise<BackgroundFetchRecord>
Tak jak powyżej, ale rozstrzyga się w przypadku pierwszego dopasowania.
Wydarzenia
progress Uruchamiane, gdy zmieni się dowolna z tych wartości: uploaded, downloaded, result lub failureReason.

Śledzenie postępów

Możesz to zrobić za pomocą zdarzenia progress. Pamiętaj, że downloadTotal to dowolna wartość, którą podasz, lub 0, jeśli jej nie podasz.

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

Uzyskiwanie żą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 i wygląda tak:

Właściwości
request Request
Przesłane żądanie.
responseReady Promise<Response>
Pobrana odpowiedź.

Odpowiedź jest przygotowana z obietnicą, ponieważ być może jeszcze do Ciebie nie dotarła. Obietnica zostanie odrzucona, jeśli pobieranie się nie powiedzie.

Zdarzenia skryptu service worker

Wydarzenia
backgroundfetchsuccess Wszystko zostało pobrane.
backgroundfetchfailure Co najmniej 1 pobieranie zakończyło się niepowodzeniem.
backgroundfetchabort Nie udało się pobrać co najmniej 1 pobrania.

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

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

Obiekty zdarzeń mają te atrybuty:

Właściwości
registration BackgroundFetchRegistration
Metody
updateUI({ title, icons }) Umożliwia zmianę początkowo ustawionego tytułu/ikony. Jest to opcjonalne, ale w razie potrzeby pozwala podać więcej kontekstu. Możesz to zrobić tylko raz podczas wydarzeń backgroundfetchsuccess i backgroundfetchfailure.

Reagowanie na sukces/porażki

Widzieliśmy już zdarzenie progress, ale jest ono przydatne tylko wtedy, gdy użytkownik ma otwartą stronę z Twoją witryną. Główną zaletą pobierania w tle jest to, że wszystko działa, gdy użytkownik opuści stronę lub nawet zamknie przeglądarkę.

Jeśli pobieranie w tle się zakończy, skrypt 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. Jeśli chcesz je zachować, przenieś je gdzieś w stylu cache API.

Tak jak w przypadku większości zdarzeń skryptu service worker, użyj event.waitUntil, aby mechanizm Service Worker wiedział o jego zakończeniu.

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

Niepowodzenie może sprowadzać się do pojedynczego błędu 404, który być może nie był dla Ciebie ważny, dlatego nadal warto skopiować niektóre odpowiedzi do pamięci podręcznej w sposób opisany powyżej.

Reakcja na kliknięcie

Interfejs, w którym widać postęp pobierania i wynik, można kliknąć. Zdarzenie backgroundfetchclick w skrypcie service worker pozwala na to zareagować. Tak jak powyżej, event.registration będzie rejestrować pobieranie w tle.

Typowe czynności związane z tym zdarzeniem to otwarcie okna:

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

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

Dodatkowe zasoby

Poprawka: poprzednia wersja tego artykułu błędnie określała pobieranie w tle jako „standardu internetowego”. Interfejs API nie jest obecnie zgodny z standardami. Specyfikację można znaleźć w WICG w formie roboczej wersji raportu dotyczącego grupy społeczności.