Error Handling
Praxis-Leitfaden für den Umgang mit Fehlern der EFA-JSON-Schnittstelle — HTTP-Ebene, Payload-Ebene, Retries.
HTTP-Statuscodes
| Status | Bedeutung | Reaktion |
|---|---|---|
200 | Erfolg (ggf. mit leeren Ergebnissen) | Payload prüfen — leere Liste ist kein Fehler |
400 | Parameterfehler | Request validieren, nicht erneut senden |
401 / 403 | Authentifizierung / Berechtigung | Credentials prüfen |
404 | Ressource unbekannt (z. B. falsche ID) | Eingabe validieren |
429 | Rate-Limit erreicht | Exponentielles Backoff |
500 / 502 / 503 / 504 | serverseitig | Retry mit Backoff, dann Fehler surfacen |
WARNING
200 OK kann trotzdem fachliche Fehler enthalten. Prüfen Sie immer beides — HTTP-Status und systemMessages[] im Payload.
Payload-Schema: systemMessages[]
Fachliche Meldungen liegen in der Antwort unter systemMessages — ein Array aus Objekten:
| Feld | Werte | Bedeutung |
|---|---|---|
code | kontextabhängig | Fehlercode (siehe Mentz-Dokument EFA9-10_Errorcodes) |
error | Freitext | Fehlerbeschreibung |
type | message | error | Klassifizierung |
module | Name | EFA-Modul, aus dem die Meldung stammt |
Beispiel:
{
"systemMessages": [
{
"code": "-4040",
"error": "keine Haltestelle gefunden",
"type": "error",
"module": "BROKER"
}
]
}NOTE
Die Fehlercode-Liste ist nicht Teil der HTTP-Parameter-Doku. Sie wird im separaten Mentz-Dokument EFA9-10_Errorcodes gepflegt — dort nachschlagen, wenn ein code-Wert untypisch wirkt.
Minimal-Wrapper
export interface SystemMessage {
code: string
error: string
type: 'message' | 'error'
module: string
}
export class EfaError extends Error {
constructor(
public httpStatus: number,
public messages: SystemMessage[] = []
) {
const first = messages.find(m => m.type === 'error') ?? messages[0]
super(first ? `[${first.code}] ${first.error}` : `EFA HTTP ${httpStatus}`)
}
}
export async function efaFetch<T>(url: string): Promise<T> {
const res = await fetch(url)
if (!res.ok) throw new EfaError(res.status)
const data = await res.json() as T & { systemMessages?: SystemMessage[] }
const errors = (data.systemMessages ?? []).filter(m => m.type === 'error')
if (errors.length) throw new EfaError(res.status, data.systemMessages)
return data
}Retry mit exponentiellem Backoff
Nur für idempotente GETs und nur bei 429 / 5xx oder Netzwerkfehlern:
export async function fetchWithRetry<T>(
url: string,
{ retries = 3, baseDelay = 400 }: { retries?: number; baseDelay?: number } = {}
): Promise<T> {
for (let attempt = 0; attempt <= retries; attempt++) {
try {
return await efaFetch<T>(url)
} catch (e) {
const retryable =
e instanceof EfaError && (e.httpStatus === 429 || e.httpStatus >= 500)
if (!retryable || attempt === retries) throw e
const jitter = Math.random() * 100
await new Promise(r => setTimeout(r, baseDelay * 2 ** attempt + jitter))
}
}
throw new Error('unreachable')
}TIP
Respektieren Sie den Retry-After-Header bei 429, falls gesetzt, statt stur zu verdoppeln.
Timeouts
Ein hängender Request ist schlimmer als ein Fehler. Immer ein Timeout setzen:
async function withTimeout(url, ms = 5000) {
const ctrl = new AbortController()
const timer = setTimeout(() => ctrl.abort(), ms)
try {
return await fetch(url, { signal: ctrl.signal })
} finally {
clearTimeout(timer)
}
}UX-Empfehlungen
- Leere Ergebnisse: neutrale Meldung („Keine Abfahrten im Zeitraum"), keine Fehler-Optik.
- 4xx: technische Meldung, keine Retry-Option anbieten.
- 429 / 5xx: automatisch retryen, optional dezenten Reconnect-Hinweis einblenden.
- Abgebrochen (
AbortError): meist still ignorieren (z. B. bei Live-Suche mit Debounce). systemMessages[]mittype: "message": Hinweise, keine Fehler — gern als Info-Banner neben dem Ergebnis.
Siehe auch
- Konzepte · IDs & Koordinaten — häufige
400-Ursachen stammen aus falschen IDs/Koordinaten. - DM-Request und DMTTP-Request — typische Live-Endpunkte, bei denen Retry-Strategien direkt spürbar werden.