Imperativer Leitfaden zum Caching

Andrew Guan
Andrew Guan
Demián Renzulli
Demián Renzulli

Einige Websites müssen möglicherweise mit dem Service Worker kommunizieren, ohne über das Ergebnis informiert zu werden. Hier einige Beispiele:

  • Eine Seite sendet dem Service Worker eine Liste von URLs, die vorab abgerufen werden sollen. Wenn der Nutzer auf einen Link klickt, sind die Dokument- oder Seitenunterressourcen bereits im Cache verfügbar, was die nachfolgende Navigation erheblich beschleunigt.
  • Die Seite bittet den Service Worker, eine Reihe von Top-Artikeln abzurufen und im Cache zu speichern, damit sie offline verfügbar sind.

Wenn Sie diese Arten von nicht kritischen Aufgaben an den Service Worker delegieren, wird der Haupt-Thread freigegeben, um dringendere Aufgaben wie die Reaktion auf Nutzerinteraktionen besser zu bewältigen.

Diagramm einer Seite, auf der Ressourcen für den Cache an einen Service Worker angefordert werden

In diesem Leitfaden erfahren Sie, wie Sie mithilfe von Standardbrowser-APIs und der Workbox-Bibliothek eine einseitige Kommunikationstechnik von der Seite zum Service Worker implementieren. Wir nennen diese Arten von Anwendungsfällen imperatives Caching.

Produktionsfall

1-800-Flowers.com hat erzwingendes Caching (Vorab-Caching) mithilfe von Service Workern über postMessage() implementiert, um die Top-Artikel auf Kategorieseiten vorab zu laden und so die nachfolgende Navigation zu Produktdetailseiten zu beschleunigen.

Logo von 1-800 Flowers

Dabei wird ein gemischter Ansatz verwendet, um zu entscheiden, welche Elemente vorab abgerufen werden sollen:

  • Beim Laden der Seite bittet er den Service Worker, die JSON-Daten für die Top 9-Artikel abzurufen und die resultierenden Antwortobjekte in den Cache aufzunehmen.
  • Bei den verbleibenden Elementen wird das Ereignis mouseover überwacht. Wenn ein Nutzer den Mauszeiger auf ein Element bewegt, kann ein Abruf der Ressource auf „Nachfrage“ ausgelöst werden.

Sie verwenden die Cache API, um JSON-Antworten zu speichern:

Logo von 1-800 Flowers
JSON-Produktdaten von Produktlistenseiten auf 1-800Flowers.com vorab abrufen

Wenn der Nutzer auf ein Element klickt, können die zugehörigen JSON-Daten aus dem Cache abgerufen werden, ohne dass eine Netzwerkverbindung erforderlich ist. Dadurch wird die Navigation beschleunigt.

Workbox verwenden

Workbox bietet eine einfache Möglichkeit, Nachrichten über das workbox-window-Paket an einen Service Worker zu senden. Dieses Paket besteht aus einer Reihe von Modulen, die im Fensterkontext ausgeführt werden sollen. Sie ergänzen die anderen Workbox-Pakete, die im Service Worker ausgeführt werden.

Um die Seite mit dem Dienstworker zu kommunizieren, müssen Sie zuerst eine Workbox-Objektreferenz auf den registrierten Dienstworker abrufen:

const wb = new Workbox('/sw.js');
wb
.register();

Anschließend können Sie die Nachricht direkt deklarativ senden, ohne sich um die Registrierung kümmern, die Aktivierung prüfen oder sich Gedanken über die zugrunde liegende Kommunikations-API machen zu müssen:

wb.messageSW({"type": "PREFETCH", "payload": {"urls": ["/data1.json", "data2.json"]}}); });

Der Dienst-Worker implementiert einen message-Handler, um diese Nachrichten zu empfangen. Optional kann eine Antwort zurückgegeben werden, in folgenden Fällen ist dies jedoch nicht erforderlich:

self.addEventListener('message', (event) => {
 
if (event.data && event.data.type === 'PREFETCH') {
   
// do something
 
}
});

Browser-APIs verwenden

Wenn die Workbox-Bibliothek nicht Ihren Anforderungen entspricht, können Sie die Kommunikation zwischen Fenster und Dienstmitarbeiter mithilfe von Browser-APIs implementieren.

Mit der postMessage API kann ein einseitiger Kommunikationsmechanismus zwischen der Seite und dem Service Worker eingerichtet werden.

Die Seite ruft postMessage() auf der Service Worker-Benutzeroberfläche auf:

navigator.serviceWorker.controller.postMessage({
  type
: 'MSG_ID',
  payload
: 'some data to perform the task',
});

Der Dienst-Worker implementiert einen message-Handler, um diese Nachrichten zu empfangen.

self.addEventListener('message', (event) => {
 
if (event.data && event.data.type === MSG_ID) {
   
// do something
 
}
});

Das {type : 'MSG_ID'}-Attribut ist nicht unbedingt erforderlich, aber es ist eine Möglichkeit, der Seite zu ermöglichen, dem Service Worker unterschiedliche Arten von Anweisungen zu senden, z. B. „to prefetch“ (vorab laden) oder „to clear storage“ (Speicher löschen). Der Dienst-Worker kann anhand dieses Flags in verschiedene Ausführungspfade verzweigen.

Wenn der Vorgang erfolgreich war, kann der Nutzer davon profitieren. Andernfalls ändert sich der grundlegende Nutzerfluss nicht. Wenn 1-800-Flowers.com beispielsweise versucht, Inhalte vorab zu cachen, muss die Seite nicht wissen, ob der Service Worker erfolgreich war. In diesem Fall kann der Nutzer schneller navigieren. Andernfalls muss die Seite zur neuen Seite weitergeleitet werden. Das dauert nur noch ein bisschen.

Ein einfaches Beispiel für das Vorabladen

Eine der häufigsten Anwendungen des imperativen Cachings ist das Prefetching, also das Abrufen von Ressourcen für eine bestimmte URL, bevor der Nutzer sie aufruft, um die Navigation zu beschleunigen.

Es gibt verschiedene Möglichkeiten, das Prefetching auf Websites zu implementieren:

  • Verwendung von Link-Prefetch-Tags auf Seiten: Ressourcen werden fünf Minuten lang im Browsercache aufbewahrt. Danach gelten die normalen Cache-Control-Regeln für die Ressource.
  • Ergänzung der vorherigen Methode durch eine Laufzeit-Caching-Strategie im Dienstworker, um die Lebensdauer der Prefetch-Ressource über dieses Limit hinaus zu verlängern.

Bei relativ einfachen Prefetch-Szenarien wie dem Prefetching von Dokumenten oder bestimmten Assets (JS, CSS usw.) sind diese Techniken der beste Ansatz.

Wenn zusätzliche Logik erforderlich ist, z. B. das Parsen der prefetch-Ressource (eine JSON-Datei oder -Seite), um die internen URLs abzurufen, sollten Sie diese Aufgabe vollständig an den Service Worker delegieren.

Die Delegierung dieser Arten von Vorgängen an den Dienst-Worker bietet folgende Vorteile:

  • Die Hauptlast des Abrufens und der Verarbeitung nach dem Abrufen (wird später vorgestellt) auf einen sekundären Thread auslagern. Dadurch wird der Hauptthread für wichtigere Aufgaben freigegeben, z. B. für die Reaktion auf Nutzerinteraktionen.
  • Mehrere Clients (z.B. Tabs) können eine gemeinsame Funktion wiederverwenden und den Dienst sogar gleichzeitig aufrufen, ohne den Hauptthread zu blockieren.

Produktdetailseiten vorab abrufen

Verwenden Sie zuerst postMessage() auf der Service Worker-Benutzeroberfläche und übergeben Sie ein Array von URLs, die im Cache gespeichert werden sollen:

navigator.serviceWorker.controller.postMessage({
  type
: 'PREFETCH',
  payload
: {
    urls
: [
     
'www.exmaple.com/apis/data_1.json',
     
'www.exmaple.com/apis/data_2.json',
   
],
 
},
});

Implementieren Sie im Service Worker einen message-Handler, um Nachrichten abzufangen und zu verarbeiten, die von einem aktiven Tab gesendet werden:

addEventListener('message', (event) => {
  let data
= event.data;
 
if (data && data.type === 'PREFETCH') {
    let urls
= data.payload.urls;
   
for (let i in urls) {
      fetchAsync
(urls[i]);
   
}
 
}
});

Im vorherigen Code haben wir eine kleine Hilfsfunktion namens fetchAsync() eingeführt, um über das Array mit URLs zu iterieren und für jede davon eine Abrufanfrage zu senden:

async function fetchAsync(url) {
 
// await response of fetch call
  let prefetched
= await fetch(url);
 
// (optionally) cache resources in the service worker storage
}

Wenn die Antwort abgerufen wurde, kannst du dich auf die Caching-Header der Ressource verlassen. In vielen Fällen, z. B. auf Produktdetailseiten, werden Ressourcen jedoch nicht im Cache gespeichert (d. h., sie haben die Cache-control-Überschrift no-cache). In solchen Fällen können Sie dieses Verhalten überschreiben, indem Sie die abgerufene Ressource im Service Worker-Cache speichern. Dies hat den zusätzlichen Vorteil, dass die Datei auch in Offlineszenarien bereitgestellt werden kann.

Mehr als nur JSON-Daten

Sobald die JSON-Daten von einem Serverendpunkt abgerufen wurden, enthalten sie oft andere URLs, die ebenfalls im Voraus abgerufen werden sollten, z. B. ein Bild oder andere Endpunktdaten, die mit diesen Daten der ersten Ebene verknüpft sind.

Angenommen, in unserem Beispiel sind die zurückgegebenen JSON-Daten die Informationen einer Website für Lebensmitteleinkäufe:

{
 
"productName": "banana",
 
"productPic": "https://cdn.example.com/product_images/banana.jpeg",
 
"unitPrice": "1.99"
 
}

Ändern Sie den fetchAsync()-Code so, dass die Liste der Produkte durchgegangen und das Hero-Image für jedes Produkt im Cache gespeichert wird:

async function fetchAsync(url, postProcess) {
 
// await response of fetch call
  let prefetched
= await fetch(url);

 
//(optionally) cache resource in the service worker cache

 
// carry out the post fetch process if supplied
 
if (postProcess) {
    await postProcess
(prefetched);
 
}
}

async
function postProcess(prefetched) {
  let productJson
= await prefetched.json();
 
if (productJson && productJson.product_pic) {
    fetchAsync
(productJson.product_pic);
 
}
}

Sie können für Situationen wie 404-Fehler eine Ausnahmebehandlung für diesen Code hinzufügen. Der Vorteil der Verwendung eines Service Workers für das Vorabladen besteht jedoch darin, dass ein Fehler ohne große Auswirkungen auf die Seite und den Hauptthread hat. Sie können auch eine ausgefeiltere Logik für die Nachverarbeitung der vorab abgerufenen Inhalte verwenden, um sie flexibler zu gestalten und von den zu verarbeitenden Daten zu entkoppeln. Alles ist möglich.

Fazit

In diesem Artikel haben wir einen gängigen Anwendungsfall für die einseitige Kommunikation zwischen Seite und Service Worker behandelt: Imperatives Caching. Die hier beschriebenen Beispiele sollen nur eine Möglichkeit zur Verwendung dieses Musters veranschaulichen. Derselbe Ansatz kann auch auf andere Anwendungsfälle angewendet werden, z. B. auf das On-Demand-Caching von Top-Artikeln für die Offlinenutzung oder das Setzen von Lesezeichen.

Weitere Muster für die Kommunikation zwischen Seiten und Dienstarbeitern finden Sie hier:

  • Updates übertragen: Die Seite wird vom Service Worker aufgerufen, um über wichtige Updates zu informieren, z.B. dass eine neue Version der Webanwendung verfügbar ist.
  • Zwei-Wege-Kommunikation: Sie können eine Aufgabe an einen Service Worker delegieren (z.B. einen umfangreichen Download) und die Seite über den Fortschritt informieren.