Start · Sprachen · JavaScript · Referenz · ServiceWorkerGlobalScope: fetch

ServiceWorkerGlobalScope: fetch

Ereignis

Wird im Service Worker ausgelöst, sobald eine kontrollierte Seite eine Netzwerk-Anfrage stellt. Mit <code>event.respondWith()</code> lässt sich die Antwort selbst liefern.

seit JavaScript Baseline: alle modernen Browser (seit ca. 2018) Kategorie: webapi

Signatur

self.addEventListener("fetch", (event) => { … })

Beschreibung

Das fetch-Ereignis tritt im ServiceWorkerGlobalScope auf, sobald eine Seite, die der Service Worker kontrolliert, eine Netzwerk-Anfrage stellt. Das gilt für Navigationen, für fetch() und XMLHttpRequest sowie für Unterressourcen wie Bilder, Skripte, Stylesheets und Schriftarten. Der Handler bekommt ein FetchEvent-Objekt. Über event.request kann man die Anfrage untersuchen und über event.respondWith() eine eigene Response zurückgeben, zum Beispiel aus dem Cache, frisch aus dem Netz oder selbst erzeugt.

Das Ereignis ist die Grundlage für Offline-Fähigkeit und Caching-Strategien in Progressive Web Apps: Cache First, Network First und Stale-While-Revalidate. Ruft der Handler respondWith() nicht auf, bearbeitet der Browser die Anfrage ganz normal über das Netzwerk.

Man kann den Handler mit self.addEventListener("fetch", handler) registrieren oder über die Eigenschaft self.onfetch = handler setzen. respondWith() muss synchron während der Ereignisbehandlung aufgerufen werden. Übergeben darf man aber ein Promise, das später zu einer Response aufgelöst wird. Hintergrundarbeiten, etwa das Aktualisieren des Caches, verlängert man mit event.waitUntil().

Parameter

Name Typ Default Beschreibung
$type Pflicht string Name des Ereignisses, hier "fetch".
$listener Pflicht (event: FetchEvent) => void Handler, der ein FetchEvent erhält. Wichtige Bestandteile: event.request (die Request), event.clientId, event.resultingClientId, event.preloadResponse (Promise, bei Navigation Preload), event.handled sowie die Methoden respondWith() und waitUntil().
$options boolean | AddEventListenerOptions false Optionale Listener-Einstellungen wie once oder signal.

Rückgabewert

Typ
void
Beschreibung
addEventListener gibt nichts zurück. Die Antwort an die Seite liefert man über event.respondWith(Response | Promise<Response>).

Beispiele

Cache First mit Netzwerk-Fallback

// sw.js
const CACHE = "app-v1";

self.addEventListener("fetch", (event) => {
  // Nur GET-Anfragen lassen sich cachen
  if (event.request.method !== "GET") return;

  event.respondWith(
    (async () => {
      const cached = await caches.match(event.request);
      if (cached) return cached;

      const response = await fetch(event.request);
      // Nur erfolgreiche Antworten vom eigenen Ursprung speichern
      if (response.ok && response.type === "basic") {
        const cache = await caches.open(CACHE);
        // Kopie ablegen, denn der Body lässt sich nur einmal lesen
        event.waitUntil(cache.put(event.request, response.clone()));
      }
      return response;
    })()
  );
});

Network First für Navigationen mit Offline-Seite

// sw.js
self.addEventListener("install", (event) => {
  event.waitUntil(
    caches.open("offline").then((cache) => cache.add("/offline.html"))
  );
});

self.onfetch = (event) => {
  if (event.request.mode !== "navigate") return;

  event.respondWith(
    (async () => {
      try {
        // Navigation Preload nutzen, falls aktiviert
        const preload = await event.preloadResponse;
        if (preload) return preload;
        return await fetch(event.request);
      } catch {
        // Offline: gespeicherte Ersatzseite ausliefern
        return caches.match("/offline.html");
      }
    })()
  );
};

Eigene Antwort für eine API-Route erzeugen

// sw.js
self.addEventListener("fetch", (event) => {
  const url = new URL(event.request.url);

  if (url.origin === self.location.origin && url.pathname === "/api/status") {
    const body = JSON.stringify({ online: navigator.onLine, zeit: Date.now() });
    event.respondWith(
      new Response(body, {
        headers: { "Content-Type": "application/json" },
      })
    );
  }
});

// In der Seite:
// const res = await fetch("/api/status");
// console.log(await res.json());
{ online: true, zeit: 1718000000000 }

// Wichtig · Fallstricke

  • respondWith() synchron aufrufen: Kommt der Aufruf erst nach einem await, wirft der Browser einen InvalidStateError. Die Anfrage geht dann ganz normal ans Netz. Deshalb die asynchrone Logik in ein Promise packen und dieses sofort übergeben.
  • Nur einmal pro Ereignis: respondWith() darf pro Ereignis nur einmal aufgerufen werden. Bei mehreren Listenern gewinnt der erste, der es aufruft.
  • Bodies lassen sich nur einmal lesen: Vor cache.put() die Antwort mit response.clone() kopieren.
  • Geltungsbereich: Der Service Worker bekommt nur Anfragen von Seiten, die in seinem Scope liegen. Beim allerersten Laden kontrolliert er die Seite noch nicht, es sei denn, man ruft clients.claim() auf.
  • Opaque Responses: Anfragen an fremde Ursprünge mit mode: \"no-cors\" liefern type === \"opaque\" mit Status 0. Solche Antworten nicht blind cachen, weil sich Fehler darin nicht erkennen lassen und sie viel Speicherplatz belegen.
  • Nicht-GET-Anfragen: Die Cache-API speichert nur GET-Anfragen. POST-Anfragen und andere Methoden besser durchreichen.
  • Sicherheit: Service Worker laufen nur in sicheren Kontexten (HTTPS oder localhost). Ein fehlerhafter Handler kann die ganze Seite unbenutzbar machen, deshalb Versionen und Aktualisierungen sorgfältig planen.
  • Kompatibilität: Wird von allen modernen Browsern unterstützt. preloadResponse (Navigation Preload) funktioniert in Safari erst ab Version 15.4.