mitmario.dev

Fehler behandeln

JavaScript Im Browser 4 Min Lesezeit 3 BeispieleLektion 3 von 7

Diese Lektion behandelt einen einzigen Satz, und der überrascht jeden beim ersten Mal:

Ein 404 ist für fetch kein Fehler.

Was das bedeutet

Ein 404 kommt ganz normal an
async function versuchen() {
  try {
    const antwort = await fetch("/lernen/api/gibtesnicht.json");

    // Diese Zeile wird erreicht. Das ist der Punkt der Lektion.
    console.log("Der try-Block läuft weiter.");
    console.log("ok:", antwort.ok);
    console.log("status:", antwort.status);
  } catch (fehler) {
    console.error("Hier kommt niemand an:", fehler.message);
  }
}

versuchen();

Wenn du eine Adresse anfragst, die es nicht gibt, dann passiert Folgendes: Die Anfrage geht raus, der Server antwortet, die Antwort kommt an. Alles hat funktioniert. Nur enthält die Antwort eben nicht das, was du wolltest, sondern die Auskunft „gibt es hier nicht”.

Aus Sicht von fetch ist das ein Erfolg. Das Promise löst auf, das await liefert eine Antwort, der try-Block läuft weiter, und dein catch sieht nichts davon.

Im ersten Beispiel steht die Ausgabe direkt untereinander: Der try-Block läuft, ok ist false, status ist 404. Das catch bleibt stumm.

Sieh dazu in den Reiter Netzwerk. Dort steht die Anfrage mit ihrem Status 404, und zwar genau in dem Moment, in dem dein Code ungerührt weiterläuft. Das ist die ganze Lektion in einem Bild: Die schlechte Nachricht ist sauber übermittelt und ordentlich zugestellt worden. Hingesehen hat nur niemand.

Klapp die Zeile auf, dann siehst du, wie ordentlich. Der Server hat Kopfzeilen mitgeschickt und einen Rumpf, und in dem steht { "fehler": "Nicht gefunden" }. Das ist eine vollständige, gut gemeinte Antwort. Sie steht deinem Code zur Verfügung, du müsstest sie nur lesen.

Das ist die häufigste Ursache für die Sorte Fehler, die erst in Produktion auffällt: Solange der Server antwortet, merkt niemand etwas. Fällt er aus oder ändert sich eine Adresse, arbeitet der Code mit einer Antwort weiter, die keine Daten enthält.

Also prüfst du selbst

Jede Antwort bringt eine Statusnummer mit, und antwort.ok fasst sie zu einem Wahrheitswert zusammen. ok ist genau dann true, wenn der Status zwischen 200 und 299 liegt.

Die vier Gruppen, in kurz:

2xx heißt: hat geklappt. 200 ist der Normalfall.

3xx heißt: liegt woanders. Weiterleitungen folgt fetch von selbst, du siehst sie meistens gar nicht.

4xx heißt: die Anfrage war das Problem. 404 gibt es nicht, 401 nicht angemeldet, 403 nicht erlaubt, 400 unverständlich.

5xx heißt: der Server hat ein Problem. 500 ist der Klassiker, 503 heißt überlastet oder in Wartung.

Die Zahl merkt man sich nicht am Stück. Was du dir merkst: Vier vorn ist deine Seite, fünf vorn ist die andere.

Das Muster, das du dir merkst

Erst prüfen, dann lesen
async function holen(adresse) {
  try {
    const antwort = await fetch(adresse);

    // Das Muster, das du dir merkst.
    if (!antwort.ok) {
      throw new Error(`Server meldet ${antwort.status}`);
    }

    const daten = await antwort.json();
    console.log("Geladen:", daten.length, "Einträge");
  } catch (fehler) {
    console.warn("Nicht geklappt:", fehler.message);
  }
}

async function beides() {
  await holen("/lernen/api/kurse.json");
  await holen("/lernen/api/gibtesnicht.json");
}

beides();

Wenn dein Aufrufer beide Fälle gleich behandeln soll, machst du aus dem schlechten Status selbst einen Fehler:

Erst fetch, dann if (!antwort.ok) throw new Error(...), dann lesen. Ab da greift ein einziges catch für beides: für die kaputte Verbindung und für die schlechte Antwort.

Wichtig ist die Reihenfolge. Die Prüfung steht vor dem await antwort.json(). Der Körper einer Fehlerantwort ist selten das, was du erwartest, und json() würde dann seinerseits werfen, nur mit einer irreführenden Meldung.

Wann das catch wirklich greift

Wenn die Verbindung selbst scheitert
// Eine Adresse auf deinem eigenen Rechner, an der niemand zuhört.
const NIRGENDWO = "http://127.0.0.1:1/daten.json";

async function versuchen() {
  try {
    const antwort = await fetch(NIRGENDWO);
    console.log("Kommt hier nie an. Status:", antwort.status);
  } catch (fehler) {
    console.warn("Das catch greift:", fehler.name);
    console.warn("Die Meldung ist je nach Browser eine andere.");
  }
}

versuchen();

Abgelehnt wird ein fetch nur, wenn die Verbindung nicht zustande kommt. Kein Netz, ein Servername, den es nicht gibt, ein Server, der nicht antwortet, oder eine Sicherheitsregel des Browsers, die die Anfrage unterbindet.

Dann bekommst du einen TypeError, und der Wortlaut der Meldung ist je nach Browser ein anderer. Verlass dich also nie auf den Text, sondern nur darauf, dass es passiert ist.

Im dritten Beispiel wird eine Adresse angefragt, an der niemand zuhört. Diesmal ist die Ausgabe genau umgekehrt: Der try-Block bricht ab, das catch meldet sich.

Und im Reiter „Netzwerk” steht der Unterschied als ein Wort. Beim 404 stand dort die Zahl 404, hier steht fehlgeschlagen, und einen Status gibt es gar nicht. Das ist keine Anzeigelaune, sondern die Sache selbst: Ein Status kommt vom Server, und hier war nie einer beteiligt.

Damit hast du die Regel als Bild. Steht eine Zahl in der Zeile, hat jemand geantwortet, und dein catch bleibt stumm. Steht fehlgeschlagen da, ist die Anfrage nie angekommen, und dann greift dein catch.

Zwei Meldungen, nicht eine

Zum Schluss etwas, das nicht technisch ist und trotzdem dazugehört.

Die Meldung für die Console und die Meldung für den Nutzer sind zwei verschiedene Texte.

In die Console gehört alles: Adresse, Status, der ursprüngliche Fehler. Du willst später nachvollziehen können, was passiert ist.

Auf die Seite gehört ein Satz, mit dem jemand etwas anfangen kann. „Die Kursliste konnte nicht geladen werden. Bitte versuch es später noch einmal.” Ein TypeError: Failed to fetch auf der Seite (Firefox: NetworkError when attempting to fetch resource.) ist keine Fehlerbehandlung, sondern eine durchgereichte Panne.

Zum Mitnehmen

Ein 404 ist für fetch kein Fehler. Das Promise löst ganz normal auf, und dein catch sieht davon nichts.

Jetzt du

Basis Konto, kostenlos

Zu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.

Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.

Was in dieser Lektion steckt

  • Artikel mit 3 Beispielen zum Ausprobieren

    Steht hier, ohne Konto lesbar.

  • Aufgabe im Editor, direkt im Browser geprüft

    Öffnet sich mit dem Basis Konto.