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 tylko część może być dostępna w pamięci w danym momencie. 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 odpowiedź strumieniową, gdy tylko wywołana zostanie funkcja event.respondWith(). Będzie ona nadal pobierać dane z tego strumienia, dopóki skrypt service worker będzie pobierać dodatkowe dane.enqueue() 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 z miejscem 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 opakowaniami 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 bardziej rozbudowana 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 podstawie ma ona charakter tradycyjnych treści wyrenderowanych na serwerze. Na przykład jeśli Twoja aplikacja internetowa korzysta z systemu zarządzania treścią, który renderuje HTML na serwerze przez sklejanie częściowych szablonów, ten model można bezpośrednio zamienić na korzystanie z odpowiedzi strumieniowych, a logika szablonów może być 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 przetwarzacza strumieniowego HTML w przeglądarce. Fragmenty kodu HTML wstawiane do dokumentu po załadowaniu strony (jak to jest powszechne 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 dość oczywista: zależy to od tego, czy masz już implementację, która korzysta z systemu CMS i częściowych szablonów (zaleta: strumień); czy oczekujesz pojedynczych dużych danych HTML, które mogłyby skorzystać z renderowania progresywnego (zaleta: strumień); czy Twoja aplikacja internetowa jest najlepiej modelowana jako aplikacja jednostronicowa (zaleta: App Shell); i czy potrzebujesz modelu, który jest obecnie obsługiwany w stabilnych wersjach wielu przeglądarek (zaleta: App Shell).

Jesteśmy jeszcze na bardzo wczesnym etapie tworzenia odpowiedzi przesyłanych strumieniowo za pomocą serwisu workera. Cieszymy się na myśl o tym, że różne modele dojrzewają, a zwłaszcza na myśl o tym, że coraz więcej narzędzi jest rozwijanych w celu 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 opowiada o szczegółach korzystania z metod start(), pull()cancel() w połączeniu, aby utworzyć strumień danych dostosowany do Twojego 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 interfejsu 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.