Wczytywanie wstępne nawigacji pozwala skrócić czas uruchamiania usługi przez równoległe wysyłanie żądań.
Podsumowanie
- W niektórych sytuacjach czas uruchamiania usługi może opóźnić odpowiedź sieci.
- Dostępna w 3 głównych silnikach przeglądarek funkcja wstępnego wczytywania nawigacji rozwiązuje ten problem, umożliwiając wysyłanie żądania równolegle z uruchamianiem pracownika usługi.
- Możesz odróżnić żądania wstępnego wczytania od zwykłej nawigacji, używając nagłówka, i wyświetlać różne treści.
Problem
Gdy przechodzisz do witryny, która używa usług workera do obsługi zdarzeń pobierania, przeglądarka prosi o odpowiedź. Polega to na uruchomieniu serwisowego workera (jeśli nie działa już) i wysłaniu zdarzenia fetch.
Czas uruchamiania zależy od urządzenia i warunków. Zwykle wynosi ona około 50 ms. Na urządzeniach mobilnych wynosi ona około 250 ms. W skrajnych przypadkach (wolne urządzenia, przeciążony procesor) może to być ponad 500 ms. Jednak ponieważ między zdarzeniami usługa robocza pozostaje aktywna przez czas określony przez przeglądarkę, opóźnienie występuje tylko sporadycznie, np. gdy użytkownik przechodzi do Twojej witryny z nowej karty lub z innej witryny.
Czas uruchamiania nie stanowi problemu, jeśli odpowiadasz z pamięci podręcznej, ponieważ korzyści z pominięcia sieci są większe niż opóźnienie uruchamiania. Jeśli jednak odpowiadasz za pomocą sieci…
Żądanie sieciowe jest opóźnione przez uruchamianie usługi.
Cały czas skracamy czas uruchamiania, stosując użytkowanie pamięci podręcznej kodu w V8, pomijanie usług workerów, które nie mają zdarzenia pobierania, uruchamianie usług workerów w sposób spekulatywny oraz inne optymalizacje. Czas uruchamiania będzie jednak zawsze większy niż zero.
Facebook zwrócił naszą uwagę na wpływ tego problemu i poprosił o sposób równoległego wykonywania żądań nawigacyjnych:
Nawigacja z wstępnym wczytaniem na ratunek
Wczytywanie wstępne nawigacji to funkcja, która pozwala określić, że „gdy użytkownik wysyła żądanie GET nawigacji, należy rozpocząć żądanie sieci podczas uruchamiania pracownika usługi”.
Opóźnienie uruchamiania nadal występuje, ale nie blokuje żądania sieci, dzięki czemu użytkownik szybciej uzyskuje dostęp do treści.
Oto film przedstawiający działanie tego rozwiązania, w którym usługa workera jest celowo opóźniana o 500 ms za pomocą pętli while:
Oto prezentacja Aby korzystać z zalet wstępnego wczytywania nawigacji, musisz mieć przeglądarkę, która obsługuje tę funkcję.
Aktywowanie wstępnego wczytania nawigacji
addEventListener('activate', event => {
event.waitUntil(async function() {
// Feature-detect
if (self.registration.navigationPreload) {
// Enable navigation preloads!
await self.registration.navigationPreload.enable();
}
}());
});
W każdej chwili możesz zadzwonić do navigationPreload.enable()
lub wyłączyć tę funkcję za pomocą navigationPreload.disable()
. Jednak ponieważ zdarzenie fetch
musi z niego korzystać, najlepiej jest je włączyć i wyłączyć w zdarzeniu activate
w usługach workera.
Korzystanie z wstępnie załadowanej odpowiedzi
Przeglądarka będzie teraz wykonywać pobieranie z wyprzedzeniem podczas nawigacji, ale nadal musisz użyć odpowiedzi:
addEventListener('fetch', event => {
event.respondWith(async function() {
// Respond from the cache if we can
const cachedResponse = await caches.match(event.request);
if (cachedResponse) return cachedResponse;
// Else, use the preloaded response, if it's there
const response = await event.preloadResponse;
if (response) return response;
// Else try the network.
return fetch(event.request);
}());
});
event.preloadResponse
to obietnica, która kończy się odpowiedzią, jeśli:
- Wstępne wczytywanie nawigacji jest włączone.
- Żądanie jest
GET
. - Żądanie to żądanie nawigacyjne (które przeglądarki generują podczas wczytywania stron, w tym iframe).
W przeciwnym razie event.preloadResponse
nadal występuje, ale jest rozwiązywane przez undefined
.
Odpowiedzi niestandardowe dla wstępnie załadowanych treści
Jeśli Twoja strona potrzebuje danych z sieci, najszybszym sposobem jest wysłanie żądania w serwisie workera i utworzenie pojedynczej odpowiedzi strumieniowej zawierającej części z pamięci podręcznej i części z sieci.
Załóżmy, że chcemy wyświetlić artykuł:
addEventListener('fetch', event => {
const url = new URL(event.request.url);
const includeURL = new URL(url);
includeURL.pathname += 'include';
if (isArticleURL(url)) {
event.respondWith(async function() {
// We're going to build a single request from multiple parts.
const parts = [
// The top of the page.
caches.match('/article-top.include'),
// The primary content
fetch(includeURL)
// A fallback if the network fails.
.catch(() => caches.match('/article-offline.include')),
// The bottom of the page
caches.match('/article-bottom.include')
];
// Merge them all together.
const {done, response} = await mergeResponses(parts);
// Wait until the stream is complete.
event.waitUntil(done);
// Return the merged response.
return response;
}());
}
});
W powyższym przykładzie mergeResponses
to mała funkcja, która łączy strumienie poszczególnych żądań. Oznacza to, że możemy wyświetlić nagłówek z poziomu pamięci podręcznej, gdy treści z sieci są przesyłane strumieniowo.
Jest to szybsze niż model „aplikacji w obudowie”, ponieważ żądanie sieciowe jest wysyłane razem z żądaniem strony, a treści mogą być przesyłane strumieniowo bez większych zmian.
Jednak żądanie includeURL
zostanie opóźnione o czas uruchamiania skryptu service worker. Aby rozwiązać ten problem, możemy też użyć wstępnego pobierania elementów nawigacji, ale w tym przypadku nie chcemy wstępnie pobrać całej strony, tylko include.
Aby to umożliwić, nagłówek jest wysyłany z każdą prośbą o wstępny odczyt:
Service-Worker-Navigation-Preload: true
Serwer może wykorzystać tę funkcję, aby wysłać różne treści w ramach żądań wstępnego wczytania nawigacji niż w ramach zwykłego żądania nawigacji. Pamiętaj tylko, aby dodać nagłówek Vary: Service-Worker-Navigation-Preload
, aby pamięć podręczna wiedziała, że Twoje odpowiedzi się różnią.
Teraz możemy użyć żądania wstępnego wczytania:
// Try to use the preload
const networkContent = Promise.resolve(event.preloadResponse)
// Else do a normal fetch
.then(r => r || fetch(includeURL))
// A fallback if the network fails.
.catch(() => caches.match('/article-offline.include'));
const parts = [
caches.match('/article-top.include'),
networkContent,
caches.match('/article-bottom')
];
Zmiana nagłówka
Domyślna wartość nagłówka Service-Worker-Navigation-Preload
to true
, ale możesz ustawić dowolną wartość:
navigator.serviceWorker.ready.then(registration => {
return registration.navigationPreload.setHeaderValue(newValue);
}).then(() => {
console.log('Done!');
});
Możesz na przykład ustawić go na identyfikator ostatniego posta, który masz w pamięci podręcznej na urządzeniu, aby serwer zwracał tylko nowsze dane.
Pobieranie stanu
Stan wstępnego wczytania nawigacji możesz sprawdzić za pomocą getState
:
navigator.serviceWorker.ready.then(registration => {
return registration.navigationPreload.getState();
}).then(state => {
console.log(state.enabled); // boolean
console.log(state.headerValue); // string
});
Dziękujemy Mattowi Falkenhagenowi i Tsuyoshij Horo za pracę nad tą funkcją i pomoc w przygotowaniu tego artykułu. Dziękujemy wszystkim, którzy przyczynili się do standaryzacji.