EMZETT.
Login

Kurz: function hole(id: number): Promise<Nutzer> { return new Promise((resolve, reject) => { setTimeout(() => (id > 0 ? resolve({ id, name: “Nutzer ” + id }) : reject(new Error(“ungültig”))), 10); }); }

Teil des Kurses TypeScript

Kapitel 11 von 15 im Kurs TypeScript. Mit Fortschritt, Quiz und Zertifikat auf der Lernseite.

Promises mit Typen

interface Nutzer { id: number; name: string }
 
function hole(id: number): Promise<Nutzer> {
  return new Promise((resolve, reject) => {
    setTimeout(() => (id > 0 ? resolve({ id, name: "Nutzer " + id }) : reject(new Error("ungültig"))), 10);
  });
}
 
async function main(): Promise<void> {
  const n = await hole(1);                 // n: Nutzer
  console.log(n.name);
  try {
    await hole(-1);
  } catch (e) {
    console.log(e instanceof Error ? e.message : "unbekannt");
  }
  const alle = await Promise.all([hole(1), hole(2)]);   // [Nutzer, Nutzer]
  console.log(alle.map((x) => x.id));
}
main();

Ausgabe:

Nutzer 1
ungültig
[ 1, 2 ]

Promise.all bewahrt die Typen eines Tupels:

async function demo() {
  const [zahl, text, flag] = await Promise.all([Promise.resolve(1), Promise.resolve("a"), Promise.resolve(true)]);
  console.log(zahl + 1, text.toUpperCase(), !flag);
}
demo();

Ausgabe:

2 A false

Fehler sind unknown

In einem catch hat die Variable den Typ unknown (mit strict). Prüfe sie, bevor du sie benutzt:

function fehlerText(e: unknown): string {
  if (e instanceof Error) return e.message;
  if (typeof e === "string") return e;
  return "Unbekannter Fehler";
}
try {
  JSON.parse("{kaputt");
} catch (e) {
  console.log(fehlerText(e).length > 0, fehlerText("Text"), fehlerText(42));
}

Ausgabe:

true Text Unbekannter Fehler

Das große Problem: Daten von außen

TypeScript-Typen gibt es nur beim Entwickeln. Wenn Daten von außen kommen (fetch, JSON.parse, Formulare, Dateien, Umgebungsvariablen), weiß niemand, ob sie wirklich so aussehen:

interface Nutzer { id: number; name: string }
const roh = '{"id": "keine Zahl", "name": 5}';
const n = JSON.parse(roh) as Nutzer;     // TypeScript glaubt dir blind
console.log(typeof n.id, n.name.toUpperCase);   // id ist ein String! Läuft trotzdem bis zum Absturz

Ausgabe:

string undefined

JSON.parse liefert any, deshalb prüft nichts. Besser: zur Laufzeit validieren.

Von Hand: Type Guards

interface Nutzer { id: number; name: string }
 
function istNutzer(x: unknown): x is Nutzer {
  return (
    typeof x === "object" && x !== null &&
    typeof (x as Record<string, unknown>).id === "number" &&
    typeof (x as Record<string, unknown>).name === "string"
  );
}
 
function parseNutzer(json: string): Nutzer {
  const daten: unknown = JSON.parse(json);
  if (!istNutzer(daten)) throw new TypeError("Kein gültiger Nutzer");
  return daten;
}
console.log(parseNutzer('{"id": 1, "name": "Mia"}'));
try { parseNutzer('{"id": "x"}'); } catch (e) { console.log((e as Error).message); }

Ausgabe:

{ id: 1, name: 'Mia' }
Kein gültiger Nutzer

Mit einer Bibliothek: Zod und Co.

Bei vielen Feldern wird das mühsam. Bibliotheken wie Zod, Valibot oder ArkType beschreiben die Form einmal und erzeugen daraus Prüfung und Typ:

import { z } from "zod";
 
const Nutzer = z.object({
  id: z.number().int().positive(),
  name: z.string().min(1),
  email: z.string().email().optional(),
});
type Nutzer = z.infer<typeof Nutzer>;       // Typ wird automatisch abgeleitet
 
const nutzer = Nutzer.parse(JSON.parse(roh));    // wirft einen verständlichen Fehler, wenn etwas nicht passt

Regel: Alles, was die Anwendungsgrenze überschreitet, wird validiert. Danach kannst du dem Typ vertrauen.

fetch mit Typen

async function ladeNutzer(id: number): Promise<Nutzer> {
  const antwort = await fetch(`/api/nutzer/${id}`);
  if (!antwort.ok) throw new Error(`HTTP ${antwort.status}`);
  const daten: unknown = await antwort.json();
  return Nutzer.parse(daten);                // Prüfung zur Laufzeit
}

antwort.json() liefert Promise<any>. Gib das Ergebnis über unknown weiter und prüfe es.

Umgebungsvariablen

const env: Record<string, string | undefined> = { PORT: "8080" };
const port = Number(env.PORT ?? 3000);
if (Number.isNaN(port)) throw new Error("PORT ungültig");
console.log(port);

Ausgabe:

8080

Timeouts und Abbruch

function mitZeitlimit<T>(p: Promise<T>, ms: number): Promise<T> {
  return Promise.race([
    p,
    new Promise<never>((_, reject) => setTimeout(() => reject(new Error("Zeitüberschreitung")), ms)),
  ]);
}
const langsam = new Promise<string>((r) => setTimeout(() => r("fertig"), 200));
mitZeitlimit(langsam, 20).catch((e: Error) => console.log(e.message));

Ausgabe:

Zeitüberschreitung

Merke

  • async-Funktionen liefern Promise<T>; Promise.all behält Tupel-Typen
  • catch (e): e ist unknown, erst mit instanceof prüfen
  • Typen existieren nur zur Entwicklungszeit: Daten von außen zur Laufzeit validieren
  • JSON.parse und response.json() liefern any: über unknown und Type Guards oder Zod prüfen
  • Mit as zu behaupten ersetzt keine Prüfung

Übungsaufgabe

Schreibe einen Type Guard für { titel: string; seiten: number } und eine Funktion, die ein JSON-Array solcher Bücher einliest und nur gültige zurückgibt.

Quiz zur Selbstkontrolle

Weiter im Kurs

Zurück: Module und die tsconfig.json

Weiter: TypeScript im Browser und in Node.js

Alle Kapitel: TypeScript im Überblick