Strategien für das Service Worker-Caching

Bisher wurden der Cache-Schnittstelle nur kleine Code-Snippets genannt. Damit Service Worker effektiv eingesetzt werden können, müssen Sie eine oder mehrere Caching-Strategien anwenden. Dafür müssen Sie etwas mit der Cache-Schnittstelle vertraut sein.

Eine Caching-Strategie ist eine Interaktion zwischen dem fetch-Ereignis eines Service Workers und der Cache-Schnittstelle. Wie eine Caching-Strategie geschrieben wird, hängt davon ab. Beispielsweise kann es besser sein, Anfragen für statische Inhalte anders als Dokumente zu verarbeiten. Dies wirkt sich auf die Zusammensetzung einer Caching-Strategie aus.

Bevor wir uns den Strategien selbst zuwenden, sehen wir uns kurz an, was die Cache-Schnittstelle nicht ist, was sie ist und welche Methoden sie zur Verwaltung von Service Worker-Caches bietet.

Die Cache-Schnittstelle im Vergleich zum HTTP-Cache

Wenn Sie die Cache-Schnittstelle bisher noch nicht verwendet haben, ist es vielleicht verlockend, sich die Schnittstelle so zu vorstellen wie den HTTP-Cache oder zumindest einen Bezug dazu zu haben. Das stimmt nicht.

  • Die Cache-Schnittstelle ist ein Caching-Mechanismus, der vollständig vom HTTP-Cache getrennt ist.
  • Jede Cache-Control-Konfiguration, die Sie zur Beeinflussung des HTTP-Cache verwenden, hat keinen Einfluss darauf, welche Assets auf der Cache-Schnittstelle gespeichert werden.

Stellen Sie sich Browser-Caches als mehrschichtige Caches vor. Der HTTP-Cache ist ein Low-Level-Cache, der von Schlüssel/Wert-Paaren mit in HTTP-Headern ausgedrückten Anweisungen gesteuert wird.

Im Gegensatz dazu ist die Cache-Oberfläche ein übergeordneter Cache, der von einer JavaScript API gesteuert wird. Dies bietet mehr Flexibilität als bei der Verwendung relativ vereinfachter HTTP-Schlüssel/Wert-Paare und macht Caching-Strategien möglich. Einige wichtige API-Methoden für Service Worker-Caches:

  • CacheStorage.open, um eine neue Cache-Instanz zu erstellen.
  • Cache.add und Cache.put, um Netzwerkantworten in einem Service Worker-Cache zu speichern.
  • Cache.match, um eine im Cache gespeicherte Antwort in einer Cache-Instanz zu finden.
  • Cache.delete, um eine im Cache gespeicherte Antwort aus einer Cache-Instanz zu entfernen.

Dies sind nur einige Beispiele. Es gibt noch weitere nützliche Methoden, aber dies sind nur die grundlegenden Methoden, die später in diesem Leitfaden verwendet werden.

Die einfache fetch-Veranstaltung

Der andere Teil einer Caching-Strategie ist das fetch-Ereignis des Service Workers. Bisher haben Sie in dieser Dokumentation etwas über das "Abfangen von Netzwerkanfragen" gehört, und das fetch-Ereignis innerhalb eines Service Workers ist der Ort, an dem dies geschieht:

// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';

self.addEventListener('install', (event) => {
  event.waitUntil(caches.open(cacheName));
});

self.addEventListener('fetch', async (event) => {
  // Is this a request for an image?
  if (event.request.destination === 'image') {
    // Open the cache
    event.respondWith(caches.open(cacheName).then((cache) => {
      // Respond with the image from the cache or from the network
      return cache.match(event.request).then((cachedResponse) => {
        return cachedResponse || fetch(event.request.url).then((fetchedResponse) => {
          // Add the network response to the cache for future visits.
          // Note: we need to make a copy of the response to save it in
          // the cache and use the original as the request response.
          cache.put(event.request, fetchedResponse.clone());

          // Return the network response
          return fetchedResponse;
        });
      });
    }));
  } else {
    return;
  }
});

Dies ist ein Spielzeugbeispiel, das Sie auch selbst in Aktion sehen können. Es bietet jedoch einen Einblick in die Funktionen von Service Workern. Der obige Code bewirkt Folgendes:

  1. Sehen Sie im Attribut destination der Anfrage nach, ob es sich um eine Bildanfrage handelt.
  2. Wenn sich das Bild im Service Worker-Cache befindet, können Sie es von dort bereitstellen. Wenn nicht, rufen Sie das Bild aus dem Netzwerk ab, speichern Sie die Antwort im Cache und geben Sie die Netzwerkantwort zurück.
  3. Alle anderen Anfragen werden ohne Interaktion mit dem Cache über den Service Worker weitergeleitet.

Das event-Objekt eines Abrufs enthält ein Attribut request, das einige nützliche Informationen enthält, mit denen Sie den Typ der einzelnen Anfragen identifizieren können:

  • url: Dies ist die URL für die Netzwerkanfrage, die derzeit vom Ereignis fetch verarbeitet wird.
  • method, die Anfragemethode (z.B. GET oder POST).
  • mode zur Beschreibung des Anfragemodus. Der Wert 'navigate' wird häufig verwendet, um Anfragen für HTML-Dokumente von anderen Anfragen zu unterscheiden.
  • destination: Hier wird der angeforderte Inhaltstyp so beschrieben, dass die Dateiendung des angeforderten Assets nicht verwendet wird.

Auch hier ist Asynchronität der Name des Spiels. Wie Sie sehen, bietet das Ereignis install eine Methode event.waitUntil, die ein Versprechen annimmt und auf deren Behebung wartet, bevor sie aktiviert wird. Das fetch-Ereignis bietet eine ähnliche event.respondWith-Methode, mit der Sie das Ergebnis einer asynchronen fetch-Anfrage oder eine Antwort zurückgeben können, die von der match-Methode der Cache-Schnittstelle zurückgegeben wird.

Caching-Strategien

Nachdem Sie sich nun mit Cache-Instanzen und dem Event-Handler fetch vertraut gemacht haben, können Sie sich mit einigen Service-Worker-Caching-Strategien beschäftigen. Die Möglichkeiten sind praktisch endlos. In diesem Leitfaden werden jedoch die Strategien berücksichtigt, die mit Workbox verfügbar sind. So erhalten Sie einen Eindruck davon, was in den internen Strukturen von Workbox vor sich geht.

Nur Cache

Zeigt den Ablauf von der Seite über den Service Worker und den Cache.

Beginnen wir mit einer einfachen Caching-Strategie, die wir "Nur Cache" nennen. Es gibt nur das: Wenn der Service Worker die Kontrolle über die Seite hat, landen übereinstimmende Anfragen immer nur im Cache. Das bedeutet, dass alle im Cache gespeicherten Assets vorab im Cache gespeichert werden müssen, damit das Muster funktioniert. Diese Assets werden erst dann im Cache aktualisiert, wenn der Service Worker aktualisiert wurde.

// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';

// Assets to precache
const precachedAssets = [
  '/possum1.jpg',
  '/possum2.jpg',
  '/possum3.jpg',
  '/possum4.jpg'
];

self.addEventListener('install', (event) => {
  // Precache assets on install
  event.waitUntil(caches.open(cacheName).then((cache) => {
    return cache.addAll(precachedAssets);
  }));
});

self.addEventListener('fetch', (event) => {
  // Is this one of our precached assets?
  const url = new URL(event.request.url);
  const isPrecachedRequest = precachedAssets.includes(url.pathname);

  if (isPrecachedRequest) {
    // Grab the precached asset from the cache
    event.respondWith(caches.open(cacheName).then((cache) => {
      return cache.match(event.request.url);
    }));
  } else {
    // Go to the network
    return;
  }
});

Oben wird bei der Installation ein Array von Assets vorab im Cache gespeichert. Wenn der Service Worker Abrufe verarbeitet, prüfen wir, ob sich die vom Ereignis fetch verarbeitete Anfrage-URL im Array der vorab im Cache gespeicherten Assets befindet. In diesem Fall rufen wir die Ressource aus dem Cache ab und überspringen das Netzwerk. Andere Anfragen werden an das Netzwerk und nur an das Netzwerk weitergeleitet. Wenn Sie diese Strategie in Aktion sehen möchten, sehen Sie sich diese Demo bei geöffneter Konsole an.

Nur Netzwerk

Zeigt den Fluss von der Seite über den Service Worker zum Netzwerk.

Das Gegenteil von „Nur Cache“ ist „Nur Netzwerk“, bei dem eine Anfrage über einen Service Worker an das Netzwerk weitergeleitet wird, ohne mit dem Service Worker-Cache zu interagieren. Das ist eine gute Strategie, um die Aktualität der Inhalte zu gewährleisten (z. B. beim Markup). Allerdings funktioniert dies nie, wenn der Nutzer offline ist.

Wenn Sie dafür sorgen, dass eine Anfrage an das Netzwerk weitergeleitet wird, müssen Sie nicht event.respondWith für eine übereinstimmende Anfrage aufrufen. Wenn Sie dies explizit angeben möchten, können Sie in Ihrem fetch-Ereignis-Callback ein leeres return;-Objekt für Anfragen platzieren, die an das Netzwerk weitergeleitet werden sollen. Dies geschieht in der Demo der Strategie „Nur Cache“ für Anfragen, die nicht vorab im Cache gespeichert werden.

Zuerst Cache, Fallback auf Netzwerk

Zeigt den Fluss von der Seite zum Service Worker, zum Cache und dann zum Netzwerk, falls nicht im Cache gespeichert.

Bei dieser Strategie geht es darum, etwas mehr ins Spiel zu bringen. Für übereinstimmende Anfragen läuft der Vorgang wie folgt ab:

  1. Die Anfrage trifft den Cache. Befindet sich das Asset im Cache, wird es von dort bereitgestellt.
  2. Wenn sich die Anfrage nicht im Cache befindet, wechseln Sie zum Netzwerk.
  3. Wenn die Netzwerkanfrage abgeschlossen ist, fügen Sie sie dem Cache hinzu und geben Sie dann die Antwort des Netzwerks zurück.

Hier ist ein Beispiel für diese Strategie, das Sie in einer Live-Demo ausprobieren können:

// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';

self.addEventListener('fetch', (event) => {
  // Check if this is a request for an image
  if (event.request.destination === 'image') {
    event.respondWith(caches.open(cacheName).then((cache) => {
      // Go to the cache first
      return cache.match(event.request.url).then((cachedResponse) => {
        // Return a cached response if we have one
        if (cachedResponse) {
          return cachedResponse;
        }

        // Otherwise, hit the network
        return fetch(event.request).then((fetchedResponse) => {
          // Add the network response to the cache for later visits
          cache.put(event.request, fetchedResponse.clone());

          // Return the network response
          return fetchedResponse;
        });
      });
    }));
  } else {
    return;
  }
});

Obwohl dieses Beispiel nur Bilder abdeckt, eignet sich dies hervorragend für alle statischen Assets (z. B. CSS, JavaScript, Bilder und Schriftarten), insbesondere solche mit Hash-Version. Sie erhöht die Geschwindigkeit für unveränderliche Assets, da alle Überprüfungen der Inhaltsaktualität mit dem Server, der möglicherweise vom HTTP-Cache gestartet wird, umgangen werden. Vor allem sind alle im Cache gespeicherten Assets offline verfügbar.

Zuerst Netzwerk, Fallback auf Cache

Zeigt den Fluss von der Seite, zum Service Worker, zum Netzwerk und dann zum Cache, wenn das Netzwerk nicht verfügbar ist.

Wenn Sie „Zuerst Cache zuerst, dann Netzwerk an zweiter Stelle“ umdrehen, haben Sie die Strategie „Netzwerk zuerst, dann Cache als zweites“, wie es sich anhört:

  1. Sie senden zuerst eine Anfrage an das Netzwerk und platzieren die Antwort in den Cache.
  2. Wenn Sie später offline sind, greifen Sie auf die neueste Version dieser Antwort im Cache zurück.

Diese Strategie eignet sich hervorragend für HTML- oder API-Anfragen, wenn Sie online die neueste Version einer Ressource benötigen, aber Offlinezugriff auf die neueste verfügbare Version gewähren möchten. Bei Anwendung auf HTML-Anfragen könnte dies wie folgt aussehen:

// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';

self.addEventListener('fetch', (event) => {
  // Check if this is a navigation request
  if (event.request.mode === 'navigate') {
    // Open the cache
    event.respondWith(caches.open(cacheName).then((cache) => {
      // Go to the network first
      return fetch(event.request.url).then((fetchedResponse) => {
        cache.put(event.request, fetchedResponse.clone());

        return fetchedResponse;
      }).catch(() => {
        // If the network is unavailable, get
        return cache.match(event.request.url);
      });
    }));
  } else {
    return;
  }
});

Sie können dies in einer Demo ausprobieren. Rufen Sie zuerst die Seite auf. Möglicherweise müssen Sie die Seite aktualisieren, bevor die HTML-Antwort im Cache gespeichert wird. Simulieren Sie dann in Ihren Entwicklertools eine Offlineverbindung und aktualisieren Sie die Seite noch einmal. Die letzte verfügbare Version wird sofort aus dem Cache bereitgestellt.

In Situationen, in denen Offlinefunktionen wichtig sind, Sie diese aber mit dem Zugriff auf die neueste Version von Markup oder API-Daten in Einklang bringen müssen, ist „Netzwerk zuerst, dann Cache zweites“ eine gute Strategie, um dieses Ziel zu erreichen.

Veraltete erneute Validierung

Zeigt den Fluss von der Seite zum Service Worker, zum Cache und dann vom Netzwerk zum Cache.

Von den Strategien, die wir bisher behandelt haben, ist "Stale-while-revalid" die komplexeste. Sie ähnelt in gewisser Weise den letzten beiden Strategien, priorisiert jedoch die Zugriffsgeschwindigkeit für eine Ressource und wird im Hintergrund auf dem neuesten Stand gehalten. Diese Strategie sieht ungefähr so aus:

  1. Rufen Sie bei der ersten Anfrage für ein Asset dieses aus dem Netzwerk ab, platzieren Sie es in den Cache und geben Sie die Netzwerkantwort zurück.
  2. Stellen Sie bei nachfolgenden Anfragen das Asset zuerst aus dem Cache und dann „im Hintergrund“ bereit, fordern Sie es noch einmal vom Netzwerk an und aktualisieren Sie den Cache-Eintrag des Assets.
  3. Bei nachfolgenden Anfragen erhalten Sie die letzte Version, die aus dem Netzwerk abgerufen wurde, die im vorherigen Schritt im Cache gespeichert wurde.

Dies ist eine hervorragende Strategie für Dinge, die irgendwie wichtig sind, um auf dem neuesten Stand zu bleiben, aber nicht unbedingt. Stellen Sie sich zum Beispiel einen Avatar für eine Social-Media-Website vor. Sie werden aktualisiert, wenn Nutzer dazu kommen, aber die neueste Version ist nicht unbedingt für jede Anfrage erforderlich.

// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';

self.addEventListener('fetch', (event) => {
  if (event.request.destination === 'image') {
    event.respondWith(caches.open(cacheName).then((cache) => {
      return cache.match(event.request).then((cachedResponse) => {
        const fetchedResponse = fetch(event.request).then((networkResponse) => {
          cache.put(event.request, networkResponse.clone());

          return networkResponse;
        });

        return cachedResponse || fetchedResponse;
      });
    }));
  } else {
    return;
  }
});

Sie können dies in einer noch einer weiteren Live-Demo in Aktion sehen, insbesondere wenn Sie den Tab „Network“ in den Entwicklertools Ihres Browsers und den CacheStorage-Viewer (falls die Entwicklertools Ihres Browsers ein solches Tool haben) beachten.

Weiter zu Workbox!

Dieses Dokument fasst unsere Übersicht über die Service Worker-API und die zugehörigen APIs zusammen. Das bedeutet, dass Sie genug über die direkte Verwendung von Service Workern gelernt haben, um mit Workbox zu experimentieren.