Was ist AJAX?
AJAX steht für Asynchronous JavaScript and XML – ein Begriff, der 2005 geprägt wurde, als Gmail und Google Maps erstmals zeigten, dass eine Seite neue Daten abrufen kann, ohne komplett neu geladen werden zu müssen. Der XML-Teil gehört größtenteils der Vergangenheit an – moderne APIs liefern JSON zurück –, aber die Kernidee bleibt dieselbe: Im Hintergrund mit einem Server kommunizieren und die Seite an Ort und Stelle aktualisieren.
Das ursprüngliche Werkzeug war XMLHttpRequest. Es funktioniert zwar, aber die API ist unhandlich und basiert auf Callbacks. Der moderne Ersatz ist die Fetch API, eine Promise-basierte Schnittstelle, die in Browsern, Node.js, Deno und Edge-Runtimes verfügbar ist.
Deine erste fetch-Anfrage
fetch nimmt eine URL entgegen und gibt ein Promise für ein Response zurück.
// basic.js
const response = await fetch("https://api.example.com/users");
const users = await response.json();
console.log(users);
Das ist bereits der gesamte „Happy Path“. Die Besonderheit dabei ist, dass fetch auch bei Fehler-Statuscodes aufgelöst wird – ein 404 wirft also keinen Fehler. Du musst die Antwort also selbst prüfen.
Das Response-Objekt
Das Response-Objekt teilt Ihnen mit, was der Server zurückgesendet hat.
// response.js
const response = await fetch("/api/users");
response.ok; // true for status 200–299
response.status; // 200, 404, 500, ...
response.statusText;
response.headers.get("content-type");
const data = await response.json(); // parse JSON
const text = await response.text(); // raw text
const blob = await response.blob(); // binary data
const form = await response.formData();
Ein Body kann nur einmal gelesen werden. Wenn Sie sowohl den rohen Text als auch das geparste JSON benötigen, rufen Sie response.clone() vor dem Lesen auf oder parsen Sie den Text selbst. Beachten Sie außerdem, dass response.json() abgelehnt wird, wenn der Body kein gültiges JSON ist – ein weiterer Grund, Aufrufe in try/catch zu kapseln.
Fehler richtig behandeln
Es gibt zwei Arten von Fehlern, die sich unterschiedlich verhalten.
- Netzwerkfehler — keine Verbindung, DNS-Fehler, blockiertes CORS.
fetchwird abgelehnt (rejects), daher werden sie voncatchabgefangen. - HTTP-Fehler — 404, 401, 500.
fetchwird aufgelöst (resolves); Sie müssenresponse.okoderresponse.statusprüfen.
// errors.js
async function getUser(id) {
try {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw new Error(`Request failed with status ${response.status}`);
}
return await response.json();
} catch (error) {
console.error("Could not load user:", error);
throw error;
}
}
Die explizite Behandlung beider Fälle ist das, was fragilen Code von resilientem Code unterscheidet. Der Leitfaden zur Fehlerbehandlung beschreibt die übergeordnete Strategie.
Daten mit POST, PUT und DELETE senden
Übergib ein Options-Objekt, um die Methode zu ändern, Header hinzuzufügen und einen Body anzuhängen.
// create.js
async function createUser(user) {
const response = await fetch("/api/users", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(user),
});
if (!response.ok) throw new Error("Could not create user");
return response.json();
}
await createUser({ name: "Ada", role: "engineer" });
Verwende PUT, um eine Ressource zu ersetzen, PATCH, um einen Teil davon zu aktualisieren, und DELETE, um sie zu entfernen. Erstelle für Datei-Uploads ein FormData-Objekt und übergib dieses als Body – der Browser setzt automatisch den korrekten Multipart-Content-Type.
Header und Authentifizierung
Header enthalten Metadaten über die Anfrage. Du kannst sie pro Request setzen, und viele APIs erwarten einen Authorization-Token.
// auth.js
const response = await fetch("/api/me", {
headers: {
Authorization: `Bearer ${token}`,
Accept: "application/json",
},
});
Hardcode niemals Secrets in clientseitigem Code – alles, was an den Browser gesendet wird, ist öffentlich. Tokens sollten aus einem Login-Flow stammen und sorgfältig gespeichert werden.
Requests abbrechen
Ein Request, der nicht mehr relevant ist – etwa weil der Nutzer ein weiteres Zeichen eingegeben hat, die Seite gewechselt hat oder die Komponente unmounted wurde –, sollte abgebrochen werden. AbortController erledigt genau das.
// abort.js
const controller = new AbortController();
fetch("/api/search?q=javascript", { signal: controller.signal })
.then((res) => res.json())
.then(console.log)
.catch((error) => {
if (error.name === "AbortError") return;
console.error(error);
});
// Cancel when it is no longer needed
controller.abort();
Da fetch keine Timeout-Option besitzt, ist AbortController ebenfalls der Standardweg, um einen Timeout zu implementieren.
// timeout.js
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 5000);
try {
const res = await fetch("/api/slow", { signal: controller.signal });
return await res.json();
} finally {
clearTimeout(timer);
}
CORS und Credentials
Browser setzen die Same-Origin Policy durch: JavaScript kann keine Antworten von einem anderen Origin lesen, es sei denn, der Server erlaubt dies explizit. Diese Freigabe erfolgt über CORS und wird vollständig auf dem Server über Response-Header wie Access-Control-Allow-Origin konfiguriert. Wenn Sie einen CORS-Fehler sehen, muss die Lösung in der Server-Konfiguration gesucht werden, nicht in Ihrem fetch-Aufruf.
Standardmäßig sendet fetch keine Cookies an Cross-Origin-URLs. Um diese einzuschließen, setzen Sie credentials: "include" und stellen Sie sicher, dass der Server Anfragen mit Credentials zulässt. Dies ist eine häufige Ursache für die Verwirrung „In Postman funktioniert es, aber nicht im Browser“.
Praktische Patterns
Loading-, Success- und Error-States. Spiegele den Request-Lifecycle immer in der UI wider.
// state.js
async function loadPosts() {
showSpinner();
try {
const res = await fetch("/api/posts");
if (!res.ok) throw new Error(`HTTP ${res.status}`);
renderPosts(await res.json());
} catch (error) {
showError("Could not load posts. Try again.");
} finally {
hideSpinner();
}
}
Debounced Search. Brich den vorherigen Request ab, sobald ein neuer startet, damit langsame Antworten keine aktuellen Ergebnisse überschreiben. Kombiniere AbortController mit einer kurzen Debounce-Verzögerung.
Parallele Requests. Nutze Promise.all, wenn mehrere Endpunkte gleichzeitig benötigt werden, und Promise.allSettled, wenn ein teilweises Ergebnis bereits nützlich ist.
Best Practices
- Überprüfe immer
response.ok, bevor du den Body parst. - Setze einen expliziten
Accept-Header und einContent-Type, wenn du einen Body sendest. - Kapsle await-Requests in
try/catchund gib eine hilfreiche Fehlermeldung aus. - Brich veraltete Requests mit
AbortControllerab. - Bündle API-Aufrufe in einem kleinen Modul, anstatt URLs über die Komponenten zu verteilen.
- Schreibe niemals Secrets in den clientseitigen Code.
- Zeige Lade- und Fehlerzustände an, damit sich die UI niemals “kaputt” anfühlt.
Häufige Fehler
- Die Annahme, dass
fetchbei einem 404- oder 500-Fehler eine Exception wirft. - Das Vergessen von
JSON.stringifyim Request-Body. - Ein fehlender
Content-Type-Header, was zu einem Parse-Error vom Server führt. - Das mehrfache Auslesen eines Response-Bodys.
- CORS zu ignorieren, bis der Browser die Anfrage in der Production-Umgebung blockiert.
- Requests nicht zu canceln, wodurch veraltete Daten einen Race Condition gewinnen.
Wie geht es weiter?
Fetch ist die Brücke zwischen deinem Front End und der Außenwelt. Kombiniere es mit dem DOM, um Ergebnisse zu rendern, async/await, um Abläufe zu steuern, und error handling, um Fehler elegant abzufangen. Vertiefe von dort aus dein Wissen über Methoden, Status-Codes und Header, da diese jede Anfrage, die du sendest, maßgeblich bestimmen.