Signatur
Beschreibung
FetchEvent ist das Ereignisobjekt, das der Browser im globalen Scope eines Service Workers (ServiceWorkerGlobalScope) auslöst. Das passiert jedes Mal, wenn eine vom Worker kontrollierte Seite eine Ressource anfordert: Dokumente, Skripte, Bilder, CSS oder Aufrufe von fetch(). Das Interface erbt von ExtendableEvent und ist die Grundlage für Offline-Fähigkeit, Caching-Strategien und Progressive Web Apps.
Der zentrale Baustein ist die Methode respondWith(). Ihr übergibt man eine Response oder ein Promise<Response>, und der Browser verwendet diese Antwort statt des normalen Netzwerkergebnisses. Ruft man respondWith() nicht auf, verarbeitet der Browser die Anfrage ganz normal. Mit waitUntil() (geerbt von ExtendableEvent) hält man den Worker aktiv, bis Hintergrundarbeiten wie ein Cache-Update abgeschlossen sind.
Wichtige Eigenschaften:
request: dasRequest-Objekt der abgefangenen Anfrage.clientId: ID des Clients (Fenster/Tab), der die Anfrage stellt.resultingClientId: ID des Clients, der bei Navigationen neu entsteht.preloadResponse:Promisemit der Antwort aus dem Navigation Preload, sofern aktiviert, sonstundefined.handled:Promise, das sich erfüllt, sobald der Browser die Anfrage abgearbeitet hat.
Im Browser erzeugt man FetchEvent praktisch nie selbst, sondern empfängt es über self.addEventListener("fetch", handler) oder self.onfetch. Den Konstruktor new FetchEvent(type, init) gibt es zwar, er ist aber vor allem für Tests gedacht.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $type Pflicht | string | Nur beim Konstruktor: der Ereignisname, in der Regel "fetch". |
|
| $init Pflicht | object | Nur beim Konstruktor: Optionsobjekt mit der Pflichtangabe request (Request) und den optionalen Angaben clientId, resultingClientId, preloadResponse, handled sowie den üblichen EventInit-Feldern. |
|
| $respondWith(response) | Response | Promise<Response> | Methode: legt die Antwort fest, die der Browser statt des Netzwerkergebnisses verwendet. Muss synchron im Handler aufgerufen werden. | |
| $waitUntil(promise) | Promise<any> | Geerbte Methode: verlängert die Lebenszeit des Workers, bis das übergebene Promise abgeschlossen ist, z. B. bei Cache-Updates im Hintergrund. |
Rückgabewert
FetchEvent zurück. respondWith() und waitUntil() geben undefined zurück.Beispiele
Cache-First-Strategie mit Netzwerk-Fallback
// sw.js
self.addEventListener("fetch", (event) => {
// Nur GET-Anfragen aus dem Cache bedienen
if (event.request.method !== "GET") return;
event.respondWith(
(async () => {
const cached = await caches.match(event.request);
if (cached) return cached;
return fetch(event.request);
})()
);
});
Stale-While-Revalidate mit waitUntil()
// sw.js
const CACHE = "app-v1";
self.addEventListener("fetch", (event) => {
const url = new URL(event.request.url);
if (!url.pathname.startsWith("/api/")) return;
const networkPromise = fetch(event.request).then(async (res) => {
const cache = await caches.open(CACHE);
await cache.put(event.request, res.clone());
return res;
});
// Worker aktiv halten, bis der Cache aktualisiert ist
event.waitUntil(networkPromise.catch(() => {}));
event.respondWith(
caches.match(event.request).then((cached) => cached || networkPromise)
);
});
Navigation Preload und Offline-Seite
// sw.js
self.addEventListener("activate", (event) => {
event.waitUntil(self.registration.navigationPreload?.enable());
});
self.onfetch = (event) => {
if (event.request.mode !== "navigate") return;
event.respondWith(
(async () => {
try {
const preloaded = await event.preloadResponse;
if (preloaded) return preloaded;
return await fetch(event.request);
} catch {
// Offline: vorab gecachte Fallback-Seite ausliefern
return (await caches.match("/offline.html")) ??
new Response("Offline", { status: 503 });
}
})()
);
};
Eigene Antwort und handled-Promise
// sw.js
self.addEventListener("fetch", (event) => {
if (new URL(event.request.url).pathname === "/ping") {
event.respondWith(
new Response(JSON.stringify({ ok: true, client: event.clientId }), {
headers: { "Content-Type": "application/json" }
})
);
event.handled.then(() => console.log("/ping beantwortet"));
}
});
// Wichtig · Fallstricke
respondWith()synchron aufrufen: Der Aufruf muss noch während der Ereignisbehandlung erfolgen. Steht er nach einemawait, wirft er einenInvalidStateError. Asynchrone Logik gehört deshalb in das übergebene Promise.respondWith()darf pro Ereignis nur einmal aufgerufen werden. Ein zweiter Aufruf löst ebenfalls einenInvalidStateErroraus.- Body-Streams lassen sich nur einmal lesen. Soll eine Antwort sowohl zurückgegeben als auch gecacht werden, braucht man
response.clone(). - Die Cache API unterstützt
cache.put()nur fürGET-Anfragen.POST- und andere Anfragen sollte man durchreichen. - Opake Antworten (Cross-Origin mit
mode: \"no-cors\") haben den Status 0. Beim Cachen belegen sie im Kontingent oft viel Speicher. - Sicherheit: Service Worker laufen nur in sicheren Kontexten (HTTPS oder
localhost). Wer Antworten zu großzügig cacht, kann Nutzern nach dem Abmelden personalisierte oder veraltete Daten ausliefern. Sensible Endpunkte sollte man daher ausnehmen. - Das erste Laden einer Seite ist noch nicht vom Worker kontrolliert. Erst nach der Aktivierung (oder mit
clients.claim()) kommenfetch-Ereignisse an. preloadResponseundhandledgibt es in allen modernen Browsern, aber erst in neueren Versionen. Vor der Verwendung mit optionaler Verkettung prüfen.