Włącz przesyłanie strumieniowe i uzyskuj natychmiastowe odpowiedzi

Każdy, kto używał usług dla robotów, wie, że są one asynchroniczne. Korzystają one wyłącznie z interfejsów opartych na zdarzeniach, takich jak FetchEvent, i używają obietnic, aby sygnalizować, że operacje asynchroniczne zostały ukończone.

Niesynchroniczność jest równie ważna, choć mniej widoczna dla dewelopera, w przypadku odpowiedzi zwracanych przez przetwarzacz zdarzeń pobierania w usługach wtyczki. Odpowiedzi strumieniowe to złoty standard: umożliwiają stronie, która wysłała pierwotne żądanie, rozpoczęcie pracy z odpowiedzią, gdy tylko będzie dostępny pierwszy fragment danych, oraz potencjalne użycie parsowników zoptymalizowanych pod kątem strumieniowego wyświetlania treści.

Podczas pisania własnego metody obsługi zdarzenia fetch zwykle przekazuje się tylko metodę respondWith() Response (lub obietnicę Response), którą się otrzymuje przez fetch() lub caches.match(). Dobra wiadomość jest taka, że Response utworzone za pomocą obu tych metod można już przesyłać strumieniowo. Złą wiadomością jest to, że „ręcznie”utworzone Response nie są dostępne do przesyłania strumieniowego, przynajmniej na razie. Właśnie w tym miejscu pojawia się interfejs Streams API.

Strumienie?

Strumień to źródło danych, które można tworzyć i modyfikować stopniowo. Zapewnia ono interfejs do odczytu lub zapisu asynchronicznych fragmentów danych, z których w danym momencie może być dostępna tylko ich część. Na razie interesują nas ReadableStream, które można wykorzystać do utworzenia obiektu Response przekazywanego do funkcji fetchEvent.respondWith():

self.addEventListener('fetch', event => {
    var stream = new ReadableStream({
    start(controller) {
        if (/* there's more data */) {
        controller.enqueue(/* your data here */);
        } else {
        controller.close();
        }
    });
    });

    var response = new Response(stream, {
    headers: {'content-type': /* your content-type here */}
    });

    event.respondWith(response);
});

Strona, której żądanie wywołało zdarzenie fetch, otrzyma strumień danych zwrotnych, gdy tylko wywołana zostanie funkcja event.respondWith(). Będzie ona nadal pobierać dane z tego strumienia, dopóki skrypt service worker będzie enqueue() dodatkowych danych. Odpowiedź przesyłana z workera usługi do strony jest asynchroniczna, a my mamy pełną kontrolę nad wypełnianiem strumienia.

Zastosowania w praktyce

Zauważyliście pewnie, że poprzedni przykład zawierał kilka komentarzy w miejscu zastępczym /* your data here */, ale nie podawał zbyt wielu szczegółów dotyczących implementacji. Jak może to wyglądać w praktyce?

Jake Archibald (nie jest to zaskoczeniem) podaje świetny przykład korzystania z strumieni do łączenia odpowiedzi HTML z wielu fragmentów HTML z pamięci podręcznej oraz danych „na żywo” przesyłanych przez fetch(). W tym przypadku są to treści do jego bloga.

Jak wyjaśnia Jake, zaletą korzystania z odpowiedzi strumieniowych jest to, że przeglądarka może analizować i renderować kod HTML w miarę jego przesyłania, w tym początkowy fragment, który jest szybko wczytywany z pamięci podręcznej, bez konieczności oczekiwania na pobranie całej treści bloga. Pozwala to w pełni wykorzystać możliwości przeglądarki w zakresie renderowania HTML-a. Z tego podejścia mogą też korzystać inne zasoby, które można renderować progresywnie, np. niektóre formaty obrazów i filmów.

Strumienie? A co z obudowami aplikacji?

Obecne sprawdzone metody korzystania z serwisów workerów do obsługi aplikacji internetowych koncentrują się na modelu powłoka aplikacji + treści dynamiczne. To podejście polega na agresywnym zapisywaniu w pamięci podręcznej „powłoki” aplikacji internetowej (minimalnej ilości kodu HTML, JavaScript i CSS potrzebnej do wyświetlenia struktury i schematu), a następnie wczytywaniu dynamicznego zawartości potrzebnej na poszczególnych stronach za pomocą żądania po stronie klienta.

Strumienie stanowią alternatywę dla modelu App Shell, w którym do przeglądarki przesyłana jest pełna odpowiedź HTML, gdy użytkownik przechodzi na nową stronę. Odpowiedź strumieniowa może korzystać z zasobów z pamięci podręcznej, dzięki czemu może szybko dostarczać początkowy fragment kodu HTML nawet w trybie offline. Jednak w rezultacie wygląda ona bardziej jak tradycyjna odpowiedź renderowana na serwerze. Jeśli na przykład Twoja aplikacja internetowa korzysta z systemu zarządzania treścią, który renderuje kod HTML na serwerze przez sklejanie częściowych szablonów, możesz bezpośrednio używać odpowiedzi strumieniowych, a logika szablonów będzie powielana w usługach workera zamiast na serwerze. Jak pokazuje ten film, w tym przypadku przewaga szybkości, jaką zapewniają odpowiedzi strumieniowe, może być znaczna:

Jedną z ważniejszych zalet przesyłania strumieniowego całej odpowiedzi HTML, która czyni z niego najszybszą alternatywę w filmie, jest to, że HTML renderowany podczas początkowego żądania nawigacji może w pełni korzystać z przeglądarkowego parsowania strumieniowego HTML. Fragmenty kodu HTML wstawiane do dokumentu po załadowaniu strony (co jest typowe w przypadku modelu App Shell) nie mogą korzystać z tej optymalizacji.

Jeśli więc jesteś na etapie planowania implementacji skryptu service worker, jaki model powinieneś zastosować: odpowiedzi strumieniowe, które są stopniowo renderowane, czy lekka powłoka połączona z żądaniem po stronie klienta dotyczącym treści dynamicznych? Odpowiedź jest prosta: to zależy. Od tego, czy masz już implementację, która korzysta z systemu CMS i częściowych szablonów (zaleta: strumień); od tego, czy oczekujesz pojedynczych dużych pakietów danych HTML, które skorzystają na renderowaniu progresywnym (zaleta: strumień); od tego, czy Twoja aplikacja internetowa najlepiej nadaje się do modelowania jako aplikacja jednostronicowa (zaleta: App Shell); i od tego, czy potrzebujesz modelu, który jest obecnie obsługiwany w stabilnych wersjach wielu przeglądarek (zaleta: App Shell).

Jesteśmy jeszcze na samym początku korzystania z przesyłanych strumieniowo odpowiedzi obsługiwanych przez service workerów i z niecierpliwością czekamy na dopracowanie różnych modeli, a zwłaszcza na opracowanie większej liczby narzędzi do automatyzacji typowych zastosowań.

Szczegółowe informacje o strumieniach

Jeśli tworzysz własne strumienie do odczytu, wywoływanie funkcji controller.enqueue() bez rozróżniania może nie być wystarczające ani wydajne. Jake omawia szczegółowo, jak metody start(), pull()cancel() można stosować razem, aby utworzyć strumień danych dostosowany do danego przypadku użycia.

Jeśli chcesz dowiedzieć się więcej, zapoznaj się ze specyfikacją strumieni.

Zgodność

Chrome 52 dodano obsługę tworzenia obiektu Response w ramach usługi roboczej przy użyciu źródła ReadableStream.

Implementacja service workera w Firefoksie nie obsługuje jeszcze odpowiedzi z użyciem ReadableStream, ale istnieje odpowiedni problem z śledzeniem dotyczący obsługi Streams API, który możesz śledzić.

Postępy w obsługiwaniu interfejsu Streams API w Edge bez prefiksu, a także ogólna obsługa interfejsu Service Worker, mogą być śledzone na stronie Stan platformy firmy Microsoft.