Przedstawiamy pobieranie w tle

Jake Archibald
Jake Archibald

W 2015 r. wprowadziliśmy synchronizację w tle, która umożliwia opóźnianie pracy przez usługę workera do momentu, gdy użytkownik uzyska połączenie z internetem. Oznacza to, że użytkownik może wpisać wiadomość, kliknąć „Wyślij” i opuścić stronę, wiedząc, że wiadomość zostanie wysłana albo teraz, albo gdy będzie miał połączenie z internetem.

Jest to przydatna funkcja, ale wymaga, aby skrypt service worker był aktywny przez cały czas 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 zabije service workera. W przeciwnym razie może to stanowić zagrożenie dla prywatności i baterii użytkownika.

Co więc możesz zrobić w sytuacji, gdy potrzebujesz pobrać coś, co zajmuje dużo czasu – np. filmu, podcastu lub poziomu gry. Do tego służy pobieranie w tle.

Pobieranie w tle jest dostępne domyślnie od wersji 74 przeglądarki Chrome.

Oto 2-minutowy film demonstrujący tradycyjny stan rzeczy w porównaniu z wykorzystaniem funkcji pobierania w tle:

Wypróbuj wersję demonstracyjną i przejrzyj kod.

Jak to działa

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

  1. Mówisz przeglądarce, aby wykonywała w tle grupę pobierania.
  2. Przeglądarka pobiera te dane, a następnie wyświetla użytkownikowi postęp.
  3. Gdy pobieranie się zakończy lub zakończy się niepowodzeniem, przeglądarka otworzy Twoje narzędzie związane z usługami i wygeneruje zdarzenie, aby poinformować Cię o wyniku. Tutaj decydujesz, co zrobić z otrzymanymi odpowiedziami.

Jeśli użytkownik zamknie strony Twojej witryny po kroku 1, nic się nie stanie – pobieranie będzie kontynuowane. Ponieważ pobieranie jest dobrze widoczne i łatwo je przerwać, nie ma obaw o prywatność w przypadku zbyt długiego zadania synchronizacji w tle. Skrypt service worker nie jest stale uruchomiony, więc nie ma obaw, że może on 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 do systemu operacyjnego.

Jeśli użytkownik rozpocznie pobieranie w trybie offline lub utraci połączenie w trakcie pobierania, pobieranie w tle zostanie wstrzymane i wznowione później.

Interfejs API

Wykrywanie funkcji

Podobnie jak w przypadku każdej nowej funkcji, musisz sprawdzić, czy przeglądarka ją obsługuje. Pobranie w tle działa tak:

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

Uruchamianie pobierania w tle

Główny interfejs API zawiesza rejestrację skryptu service worker, dlatego najpierw musisz go zarejestrować. 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
identyfikuje w wyjątkowy sposób to pobieranie w tle.

backgroundFetch.fetch zostanie odrzucony, jeśli identyfikator pasuje do identyfikatora z dotychczasowego pobierania w tle.

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

Możesz pobierać dane z innych źródeł, o ile pozwalają na to te zasoby za pomocą CORS.

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

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

Chociaż jest to opcjonalne, zdecydowanie zalecamy podanie tej informacji. Jest ona używana do informowania użytkownika o rozmiarze pliku do pobrania i postępie pobierania. Jeśli go nie podasz, przeglądarka poinformuje użytkownika, że rozmiar jest nieznany, co może zwiększyć prawdopodobieństwo przerwania pobierania.

Jeśli liczba pobrań w tle przekroczy podany tu limit, pobieranie zostanie przerwane. Nie ma znaczenia, czy pobieranie jest mniejsze niż downloadTotal. Jeśli nie masz pewności, ile będzie wynosić łączny rozmiar pobierania, lepiej zachować ostrożność.

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

Przesyłanie wielu żądań w ramach pojedynczego pobierania w tle umożliwia połączenie elementów, które z logicznego punktu widzenia są dla użytkownika jedną rzeczą. Film może na przykład składać się z tysiąca zasobów (co jest typowe dla MPEG-DASH) oraz 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 chodzi tylko o „film”, „poziom”.

Uzyskiwanie istniejącego pobierania w tle

Możesz uzyskać istniejące pobieranie w tle w ten sposób:

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

…przekazując id pobierania w tle, które chcesz wykonać. Funkcja get zwraca undefined, jeśli nie ma aktywnego pobierania w tle z tym identyfikatorem.

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

Listę wszystkich aktywnych pobierania w tle możesz uzyskać za pomocą getIds:

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

Rejestracje pobierania w tle

Element BackgroundFetchRegistration (bgFetch w przypadkach powyżej) ma:

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, które zostały wysłane.
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 utracone i pobieranie nie może zostać wznowione, przeglądarka rozpocznie pobieranie tego zasobu od początku.

result

Jedna z tych wartości:

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

Jedna z tych wartości:

  • "" – pobieranie w tle się nie zakończyło niepowodzeniem.
  • "aborted" – pobieranie w tle zostało przerwane przez użytkownika lub wywołano abort().
  • "bad-status" – jedna z odpowiedzi miała stan nieprawidłowy, np. 404.
  • "fetch-error" – jedno z pobierania zakończyło się niepowodzeniem z innego powodu, np. CORS, MIX, nieprawidłowej częściowej odpowiedzi lub ogólnej awarii sieci, której nie można powtórzyć.
  • "quota-exceeded" – limit miejsca na dane został osiągnięty podczas pobierania w tle.
  • "download-total-exceeded" – została przekroczona wartość „downloadTotal”.
recordsAvailable boolean
Czy można uzyskać dostęp do żądań i odpowiedzi?

Gdy ma wartość Fałsz, nie można używać match ani matchAll.

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

Zwrócona obietnica zwraca wartość true, jeśli pobranie zostało przerwane.

matchAll(request, opts) Zwraca Promise<Array<BackgroundFetchRecord>>
Get the requests and responses.

Argumenty są takie same jak w interfejsie 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>
Jak wyżej, ale rozwiązuje z pierwszym dopasowaniem.
Wydarzenia
progress Wywoływany, gdy zmieni się wartość właściwości uploaded, downloaded, result lub failureReason.

Śledzenie postępów

Można to zrobić za pomocą zdarzenia progress. Pamiętaj, że downloadTotal to dowolna podana wartość lub 0, jeśli wartość nie została podana.

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

Pobieranie żą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
Prośba, która została przesłana.
responseReady Promise<Response>
Pobrana odpowiedź.

Odpowiedź jest opóźniona, ponieważ może jeszcze nie zostać otrzymana. Jeśli pobranie się nie powiedzie, obietnica zostanie odrzucona.

Zdarzenia Service Worker

Wydarzenia
backgroundfetchsuccess Udało się pobrać wszystko.
backgroundfetchfailure Nie udało się pobrać co najmniej jednego pliku.
backgroundfetchabort Co najmniej 1 pobieranie zakończyło się niepowodzeniem.

Jest to przydatne tylko wtedy, gdy chcesz usunąć powiązane dane.

backgroundfetchclick Użytkownik kliknął interfejs użytkownika pokazujący postęp pobierania.

Obiekty zdarzeń mają:

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

Odpowiedź na powodzenie lub niepowodzenie

Zdarzenie progress jest już dostępne, ale jest przydatne tylko wtedy, gdy użytkownik ma otwartą stronę w Twojej witrynie. 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 zostanie ukończone, usługa robocza otrzyma zdarzenie backgroundfetchsuccess, a event.registration będzie rejestracją pobierania w tle.

Po tym zdarzeniu pobrane żądania i odpowiedzi nie są już dostępne, więc jeśli chcesz je zachować, przenieś je do interfejsu API pamięci podręcznej.

Podobnie jak w przypadku większości zdarzeń skryptu service worker, należy użyć event.waitUntil, aby taki skrypt znał powiadomienie o zakończeniu zdarzenia.

Na przykład w usługach:

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 być spowodowane pojedynczym błędem 404, który może nie być dla Ciebie ważny, więc warto skopiować niektóre odpowiedzi do pamięci podręcznej, jak opisano powyżej.

Reakcja na kliknięcie

Interfejs wyświetlający postęp i wynik pobierania jest klikalny. Zdarzenie backgroundfetchclick w usługowym workerze pozwala na reakcję na to zdarzenie. Jak wspomniano powyżej, event.registration będzie rejestracją pobierania w tle.

Zwykle w przypadku tego zdarzenia otwiera się okno:

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

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

Dodatkowe materiały

Poprawka: w poprzedniej wersji tego artykułu nieprawidłowo określono pobieranie w tle jako „standard sieci”. Interfejs API nie jest obecnie standardem, ale specyfikację można znaleźć w WICG jako raport grupy roboczej.