almessadi.
Zur Übersicht

Ergebnistypen für erwartete Fehler in TypeScript_

Ausnahmen haben immer noch ihren Platz, aber erwartete Geschäftsfehler sind einfacher zu verstehen, wenn sie als Daten modelliert werden, anstatt in Kontrollflüssen versteckt zu sein.

Veröffentlicht12. April 2024
Lesezeit7 min read

TypeScript bietet starke Garantien für viele Arten von ungültigen Eingaben.

Was es nicht tut, ist, geworfene Ausnahmen auf nützliche Weise durch Ihr Programm zu verfolgen.

Deshalb lesen sich erwartete Fehler oft klarer als Daten als als Ausnahmen.

Nicht jeder Fehler sollte eine Ausnahme auslösen

Es gibt einen Unterschied zwischen:

  • "das Zahlungsgateway hat card_declined zurückgegeben"
  • "unser Prozess hat eine Invariante verletzt, die niemals passieren sollte"

Das erste gehört zum normalen Geschäftsablauf.
Das zweite ist außergewöhnlich.

Wenn Sie in beiden Fällen eine Ausnahme auslösen, müssen die Aufrufer zu viel aus dem Kontrollfluss ableiten.

Ein einfacher Ergebnistyp

Sie benötigen keine Kategorientheorie, um die Idee gut zu nutzen. In TypeScript genügt eine diskriminierte Union:

type Result<T, E> =
  | { ok: true; value: T }
  | { ok: false; error: E };

async function fetchUser(id: string): Promise<Result<User, "NOT_FOUND" | "NETWORK_ERROR">> {
  const response = await fetch(`/api/users/${id}`);

  if (response.status === 404) {
    return { ok: false, error: "NOT_FOUND" };
  }

  if (!response.ok) {
    return { ok: false, error: "NETWORK_ERROR" };
  }

  return { ok: true, value: await response.json() };
}

Jetzt sagt die Funktionssignatur dem Aufrufer, dass ein Fehler erwartet und modelliert ist.

Was Sie gewinnen

Dieser Ansatz hilft, wenn:

  • die Fehlermodi Teil der Domänenlogik sind
  • vom Aufrufer erwartet wird, auf jeden einzelnen anders zu reagieren
  • Sie möchten, dass diese Fälle im Typsystem sichtbar sind

Der Aufrufort wird explizit:

const result = await fetchUser("123");

if (!result.ok) {
  if (result.error === "NOT_FOUND") return render404();
  return renderRetryState();
}

return renderProfile(result.value);

Das ist oft einfacher zu überprüfen als ein try/catch, das Transportfehler, Parserfehler und Geschäftslogikfehler in einem Block mischt.

Was Sie dennoch auslösen sollten

Ergebnistypen sind kein Grund, Ausnahmen zu verbannen.

Werfen Sie eine Ausnahme, wenn:

  • der Prozess eine wahre Invarianzverletzung aufgedeckt hat
  • die Fortsetzung der Ausführung unsicher ist
  • eine Rahmenbedingung Ausnahmen erwartet

Modellieren Sie als Daten, wenn:

  • der Fehler erwartet wird
  • der Aufrufer darauf basierend verzweigen muss
  • es Teil des normalen Produktverhaltens ist

Diese Linie ist nützlicher als die pauschale Regel "Ausnahmen sind schlecht".

Weiterführende Informationen