Jeder, der Dienstworker verwendet hat, weiß, dass sie bis ins letzte asynchron sind. Sie basieren ausschließlich auf ereignisbasierten Schnittstellen wie FetchEvent
und verwenden Versprechen, um anzuzeigen, wann asynchrone Vorgänge abgeschlossen sind.
Die asynchrone Ausführung ist ebenso wichtig, wenn es um Antworten geht, die vom Fetch-Ereignishandler eines Service Workers bereitgestellt werden. Für Entwickler ist dies jedoch weniger sichtbar. Streamingantworten sind hier der Goldstandard: Sie ermöglichen es der Seite, auf der die ursprüngliche Anfrage gesendet wurde, mit der Antwort zu arbeiten, sobald der erste Datenblock verfügbar ist. Außerdem können Parser verwendet werden, die für das Streaming optimiert sind, um die Inhalte schrittweise anzuzeigen.
Wenn Sie Ihren eigenen fetch
-Ereignishandler schreiben, übergeben Sie der Methode respondWith()
in der Regel einfach einen Response
(oder ein Versprechen für einen Response
), den Sie über fetch()
oder caches.match()
erhalten. Die gute Nachricht ist, dass die mit beiden Methoden erstellten Response
s bereits gestreamt werden können. Die schlechte Nachricht ist, dass „manuell“ erstellte
Response
s zumindest bis jetzt nicht gestreamt werden können. Hier kommt die Streams API ins Spiel.
Streams?
Ein Stream ist eine Datenquelle, die inkrementell erstellt und manipuliert werden kann. Sie bietet eine Schnittstelle zum Lesen oder Schreiben asynchroner Datenblöcke, von denen jeweils nur ein Teil im Arbeitsspeicher verfügbar sein kann. Im Moment interessieren wir uns für ReadableStream
s, mit denen ein Response
-Objekt erstellt werden kann, das an fetchEvent.respondWith()
übergeben wird:
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);
});
Die Seite, deren Anfrage das fetch
-Ereignis ausgelöst hat, erhält eine Streamingantwort, sobald event.respondWith()
aufgerufen wird. Sie liest dann so lange aus diesem Stream, wie der Service Worker weitere Daten enqueue()
. Die Antwort, die vom Service Worker an die Seite gesendet wird, ist wirklich asynchron und wir haben die vollständige Kontrolle über das Ausfüllen des Streams.
Anwendungsfälle aus der Praxis
Sie haben wahrscheinlich bemerkt, dass das vorherige Beispiel einige Platzhalter/* your data here */
-Kommentare enthielt und es nur wenige Details zur tatsächlichen Implementierung gab.
Wie würde ein Beispiel aus der Praxis aussehen?
Jake Archibald hat (wie nicht anders zu erwarten) ein hervorragendes Beispiel dafür, wie mithilfe von Streams eine HTML-Antwort aus mehreren im Cache gespeicherten HTML-Snippets zusammengesetzt wird, zusammen mit „Live“-Daten, die über fetch()
gestreamt werden – in diesem Fall Inhalte für seinen Blog.
Der Vorteil einer Streaming-Antwort besteht darin, wie Jake erklärt, dass der Browser den HTML-Code beim Streamen parsen und rendern kann, einschließlich des ersten Bits, das schnell aus dem Cache geladen wird, ohne dass der gesamte Bloginhalt abgerufen werden muss. So werden die progressiven HTML-Rendering-Funktionen des Browsers optimal genutzt. Auch andere Ressourcen, die sich progressiv rendern lassen, wie einige Bild- und Videoformate, können von diesem Ansatz profitieren.
Streams? Oder App-Shells?
Die bestehenden Best Practices für die Verwendung von Dienst-Workern zur Unterstützung Ihrer Web-Apps konzentrieren sich auf ein Modell mit App-Shell und dynamischen Inhalten. Bei diesem Ansatz wird die „Shell“ Ihrer Webanwendung (die minimale HTML-, JavaScript- und CSS-Datei, die zum Darstellen der Struktur und des Layouts erforderlich ist) aggressiv im Cache gespeichert und dann die dynamischen Inhalte, die für jede Seite erforderlich sind, über eine clientseitige Anfrage geladen.
Streams bieten eine Alternative zum App-Shell-Modell, bei der eine vollständigere HTML-Antwort an den Browser gestreamt wird, wenn ein Nutzer eine neue Seite aufruft. Die gestreamte Antwort kann auf im Cache gespeicherte Ressourcen zurückgreifen. So kann der erste HTML-Abschnitt auch offline schnell bereitgestellt werden. Sie sehen jedoch eher aus wie herkömmliche, serverseitig gerenderte Antwortkörper. Wenn Ihre Webanwendung beispielsweise von einem Content-Management-System unterstützt wird, das HTML-Seiten durch Zusammenfügen von Teilvorlagen auf dem Server rendert, wird dieses Modell direkt in Streamingantworten umgesetzt, wobei die Vorlagenlogik im Service Worker statt auf dem Server repliziert wird. Wie das folgende Video zeigt, kann der Geschwindigkeitsvorteil von gestreamten Antworten bei diesem Anwendungsfall beträchtlich sein:
Ein wichtiger Vorteil des Streamings der gesamten HTML-Antwort, der erklärt, warum dies die schnellste Alternative im Video ist, besteht darin, dass beim Rendern von HTML während der ersten Navigationsanfrage der Streaming-HTML-Parser des Browsers voll ausgenutzt werden kann. HTML-Chunks, die nach dem Laden der Seite in ein Dokument eingefügt werden (wie es beim App-Shell-Modell üblich ist), können von dieser Optimierung nicht profitieren.
Welches Modell sollten Sie also in der Planungsphase Ihrer Service Worker-Implementierung verwenden: gestreamte Antworten, die schrittweise gerendert werden, oder eine schlanke Shell in Verbindung mit einer clientseitigen Anfrage für dynamische Inhalte? Die Antwort ist nicht überraschend: Es kommt darauf an, ob Sie eine vorhandene Implementierung haben, die auf einem CMS und teilweisen Vorlagen basiert (Vorteil: Stream), ob Sie einzelne, große HTML-Nutzlastdaten erwarten, die von progressivem Rendering profitieren würden (Vorteil: Stream), ob Ihre Webanwendung am besten als Single-Page-Anwendung modelliert werden sollte (Vorteil: App-Shell) und ob Sie ein Modell benötigen, das derzeit in den stabilen Releases mehrerer Browser unterstützt wird (Vorteil: App-Shell).
Wir befinden uns noch in der Anfangsphase von Streamingantworten, die von Service Workern unterstützt werden. Wir freuen uns darauf, die verschiedenen Modelle weiterzuentwickeln und vor allem mehr Tools zur Automatisierung gängiger Anwendungsfälle zu entwickeln.
Streams im Detail ansehen
Wenn Sie eigene lesbare Streams erstellen, ist es möglicherweise nicht ausreichend oder effizient, controller.enqueue()
einfach unvoreingenommen aufzurufen. Jake erklärt ausführlich, wie die Methoden start()
, pull()
und cancel()
kombiniert werden können, um einen auf Ihren Anwendungsfall zugeschnittenen Datenstream zu erstellen.
Weitere Informationen findest du in der Streams-Spezifikation.
Kompatibilität
In Chrome 52 wurde die Unterstützung für das Erstellen eines Response
-Objekts in einem Service Worker mit einer ReadableStream
als Quelle hinzugefügt.
Die Service Worker-Implementierung von Firefox unterstützt noch keine Antworten, die von ReadableStream
s unterstützt werden. Es gibt jedoch einen relevanten Tracking-Fehler für den Support der Streams API, den du verfolgen kannst.
Informationen zum Fortschritt bei der Unterstützung der Streams API ohne Präfix in Edge sowie zur allgemeinen Unterstützung von Service Workers finden Sie auf der Plattformstatusseite von Microsoft.