Fetch ist ein promise-basierter HTTP-Client
Die fetch-API ist fest in Browser und moderne Node-Versionen eingebaut. Du übergibst ihr eine URL, und zurück bekommst du ein Promise, das zu einem Response-Objekt auflöst. Im Kern ist das schon die gesamte API:
Zwei .then-Aufrufe, weil hier zwei asynchrone Schritte ablaufen: Zuerst treffen die Response-Header ein (damit wird das erste Promise aufgelöst), danach wird der Body gelesen und geparst (response.json() liefert selbst wieder ein Promise). Der Body wird übrigens erst heruntergeladen, wenn du ihn tatsächlich anforderst.
Mit async/await liest sich derselbe Ablauf wie ganz gewöhnlicher Code von oben nach unten:
Zwei await, zwei Unterbrechungspunkte. Gleiche Arbeit, aber die Lesereihenfolge ist klarer.
Das Response-Objekt
Was du zurückbekommst, ist nicht direkt der Body, sondern ein Response-Objekt mit Metadaten und Methoden, um den Body in verschiedenen Formaten auszulesen:
Du kannst den Body mit .json(), .text(), .blob(), .arrayBuffer() oder .formData() auslesen. Jede dieser Methoden gibt ein Promise zurück. Wichtig: Den Body kannst du nur einmal lesen – ein zweiter Aufruf von .json() auf derselben Response wirft einen Fehler.
Die große Stolperfalle: HTTP-Fehler lösen kein reject aus
Genau hier tappen fast alle rein, die neu mit der Fetch API arbeiten. Eine Antwort mit Status 404 oder 500 ist kein Rejection. Das Promise wird ganz normal aufgelöst, nur eben mit response.ok === false. Fetch schlägt erst dann wirklich fehl, wenn der Request gar nicht erst zustande kommt – also bei DNS-Problemen, fehlender Netzwerkverbindung oder einem CORS-Block.
Konkret heißt das: Ein naiv geschriebener Fetch-Aufruf reicht dir fröhlich eine Fehlerseite durch und kracht erst später beim .json()-Parsen:
Die Lösung: Du musst response.ok selbst prüfen und einen Fehler werfen, falls der Server einen Error-Status zurückgibt:
Gewöhn dir an, diesen if (!response.ok)-Block immer zu schreiben. Er gehört in jeden Fetch-Wrapper, den du baust.
POST-Request mit fetch senden
GET ist die Voreinstellung. Für alles andere übergibst du als zweites Argument ein Options-Objekt:
Drei Dinge, die hier wichtig sind:
methodist standardmäßig"GET". Für POST, PUT, DELETE oder PATCH musst du die Methode explizit setzen.bodyerwartet einen String (oderFormData,Blobusw.) — fetch serialisiert Objekte nicht automatisch. DasJSON.stringify(...)ist deine Aufgabe.- Der Header
Content-Typesagt dem Server, wie er den Body interpretieren soll. Lässt du ihn weg, behandeln die meisten Server den Inhalt als reinen Text.
Fetch Headers setzen, Query-Strings und weitere Optionen
Headers sind einfach ein Objekt (oder eine Headers-Instanz). Query-Strings baust du dir selbst zusammen, meistens mit URLSearchParams:
URLSearchParams kümmert sich um das Encoding — Leerzeichen, Ampersands, Unicode — damit du keine kaputten URLs bekommst, wenn die Eingabe Zeichen enthält, die escaped werden müssen.
Weitere Optionen, die dir in echtem Code begegnen: credentials: "include", um Cookies auch Cross-Origin mitzuschicken, cache: "no-store", um den HTTP-Cache zu umgehen, und mode: "cors" (meist der Default), um das CORS-Verhalten zu steuern.
Requests abbrechen mit AbortController
Manchmal willst du einfach abbrechen — der User hat eine neue Suchanfrage getippt, oder der Request dauert viel zu lange. Genau dafür gibt es den AbortController:
controller.abort() sorgt dafür, dass das Fetch-Promise mit einer DOMException abgelehnt wird, deren name "AbortError" ist. Der finally-Block räumt den Timeout wieder ab, damit bei einer erfolgreichen Anfrage kein Timer verwaist zurückbleibt.
Dieses Muster – Fetch plus Timeout plus Aufräumen – lohnt es sich in einen Helper zu packen und überall wiederzuverwenden.
Ein wiederverwendbarer Wrapper
Wenn man alles zusammensetzt, bekommt man einen kleinen Helper, der den Boilerplate-Code ein für alle Mal erledigt:
Eine zentrale Stelle für Header, eine für Fehler, eine für leere Responses. Jede nicht-triviale App landet früher oder später bei sowas.
Als Nächstes: Fehlerbehandlung in asynchronem Code
fetch ist einer der typischen Orte, an denen asynchrone Fehler auftauchen, und der response.ok-Check ist dabei nur ein Puzzleteil. Auf der nächsten Seite geht's um Fehlerbehandlung quer durch Promises und async/await – wohin Fehler eigentlich wandern, wie man sie abfängt und welche Fallstricke dafür sorgen, dass sie klammheimlich verschwinden.
Häufig gestellte Fragen
Wie benutze ich fetch in JavaScript?
Du rufst fetch(url) mit der gewünschten URL auf. Zurück kommt ein Promise, das zu einem Response-Objekt auflöst. Den Body parst du mit response.json() – auch das ist wieder ein Promise. Mit async/await sieht das so aus: const res = await fetch(url); const data = await res.json();.
Wie sende ich einen POST-Request mit fetch?
Du übergibst ein zweites Argument mit method: 'POST', einem headers-Objekt (meist 'Content-Type': 'application/json') und einem body. Objekte musst du selbst mit JSON.stringify(...) in einen String umwandeln – fetch serialisiert den Body nicht automatisch für dich.
Warum schlägt fetch bei 404 oder 500 nicht fehl?
Das Promise von fetch wird nur bei echten Netzwerkfehlern abgelehnt – also DNS-Problemen, fehlender Verbindung oder CORS-Blockaden. HTTP-Statuscodes wie 404 oder 500 zählen technisch gesehen als erfolgreiche Antwort. Du musst selbst response.ok prüfen (true bei 200–299) oder response.status auswerten und bei Bedarf eine Exception werfen.
Kann man einen fetch-Request abbrechen?
Ja, dafür gibt es den AbortController. Du legst einen an, übergibst dessen signal per Options-Objekt an fetch und rufst bei Bedarf controller.abort() auf. Das fetch-Promise wird dann mit einem AbortError abgelehnt, den du im catch-Block abfangen kannst.