Skip to content

Error Handling

Praxis-Leitfaden für den Umgang mit Fehlern der EFA-JSON-Schnittstelle — HTTP-Ebene, Payload-Ebene, Retries.

HTTP-Statuscodes

StatusBedeutungReaktion
200Erfolg (ggf. mit leeren Ergebnissen)Payload prüfen — leere Liste ist kein Fehler
400ParameterfehlerRequest validieren, nicht erneut senden
401 / 403Authentifizierung / BerechtigungCredentials prüfen
404Ressource unbekannt (z. B. falsche ID)Eingabe validieren
429Rate-Limit erreichtExponentielles Backoff
500 / 502 / 503 / 504serverseitigRetry 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:

FeldWerteBedeutung
codekontextabhängigFehlercode (siehe Mentz-Dokument EFA9-10_Errorcodes)
errorFreitextFehlerbeschreibung
typemessage | errorKlassifizierung
moduleNameEFA-Modul, aus dem die Meldung stammt

Beispiel:

json
{
  "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

ts
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:

ts
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:

js
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[] mit type: "message": Hinweise, keine Fehler — gern als Info-Banner neben dem Ergebnis.

Siehe auch