Menu

TypeScript never: Vollständigkeitsprüfung und never vs void

never ist der Typ ohne Werte. Er ist der Rückgabetyp von Funktionen, die nie fertig werden, der Typ, der übrig bleibt, wenn Narrowing jeden Fall ausgeschlossen hat, und das Werkzeug hinter vollständigen switch-Prüfungen. Woher er kommt und wie er sich von void unterscheidet.

Diese Seite enthält ausführbare Editoren - bearbeiten, ausführen und Ausgabe sofort sehen.

never ist der Typ ohne Werte. Eine Funktion mit dem Rückgabetyp never kehrt nie normal zurück: Sie wirft oder läuft endlos. Eine Variable vom Typ never markiert Code, der nicht laufen kann, und genau das macht Vollständigkeitsprüfungen möglich.

return fail(...) kompiliert in einer Funktion, die number zurückgibt, weil never jedem Typ zuweisbar ist: Ein Wert vom Typ never kann nie tatsächlich ankommen.

Funktionen, die nie zurückkehren

Zwei Arten von Funktionen erreichen nie ihr Ende: eine, die immer wirft, und eine mit Endlosschleife. Der Compiler prüft die Behauptung. Eine als never annotierte Funktion, deren Ende erreichbar ist, ist der Fehler TS2534, A function returning 'never' cannot have a reachable end point.

function fail(message: string): never {
  throw new Error(message);
}

function runForever(): never {
  while (true) {
    // poll, serve requests...
  }
}

Die Inferenz hängt von der Syntax ab. Eine Funktionsdeklaration, die nur wirft, wird mit Rückgabe void abgeleitet, während eine Arrow Function oder ein Funktionsausdruck, der nur wirft, als never abgeleitet wird:

function f1() { throw new Error("x"); }       // () => void
const f2 = () => { throw new Error("x"); };   // () => never

Das Narrowing behandelt einen Aufruf nur dann als Sackgasse, wenn der aufgerufene Name einen ausdrücklichen Typ hat, der never zurückgibt: eine Funktionsdeklaration mit Annotation : never, wie fail oben, oder eine Variable mit Typannotation, const fail: (m: string) => never = (m) => { throw new Error(m); }. Ein abgeleitetes never zählt nicht, und ebenso wenig const fail = (m: string): never => ..., wo nur die Arrow Function annotiert ist, die Variable aber nicht.

never vs void

voidnever
Funktion endetjanein (wirft oder läuft endlos)
Wert zur Laufzeitundefinedkeiner: Der Aufruf erzeugt nie einen
Code nach dem Aufruferreichbarunerreichbar
Anderen Typen zuweisbarnur void, unknown, anyjedem Typ
Typische VerwendungCallbacks, Event-Handler, Funktionen mit Nebeneffektenfail(), assertNever(), Endlosschleifen

Der praktische Unterschied zeigt sich beim Narrowing. Nach if (!user) fail("no user") weiß der Compiler nur dann, dass user in der nächsten Zeile definiert ist, wenn fail never zurückgibt. Bei einer Rückgabe void nimmt er an, dass die Ausführung weitergehen kann.

Vollständigkeitsprüfung mit never

Jeder case eines switch über eine Union engt den Wert ein. Ist jeder Member behandelt, bleibt im default never übrig. Ihn einer never-Variablen zuzuweisen macht aus „ich habe jeden Fall behandelt“ etwas, das der Compiler prüft:

Füge der Union nun einen dritten Member hinzu, ohne einen case zu ergänzen:

index.ts(18,26): error TS2345: Argument of type '{ kind: "triangle"; base: number; height: number; }' is not assignable to parameter of type 'never'.

Der Fehler nennt den vergessenen Member. Ergänze case "triangle": return (shape.base * shape.height) / 2;, dann kompiliert es wieder. Die Hilfsfunktion assertNever ist die wiederverwendbare Form derselben Prüfung, und ihr throw zählt zur Laufzeit weiterhin: Daten aus JSON oder von einem älteren Client können eine kind enthalten, die laut Typen unmöglich ist. Dieses Muster ist das Rückgrat von Discriminated Unions.

Bis zu never einengen

Dasselbe passiert bei jedem Narrowing, nicht nur bei switch. Sobald jede Möglichkeit ausgeschlossen ist, hat die Variable den Typ never:

Erweiterst du den Parameter später auf string | number | boolean | bigint, wird die Zeile const nothing: never = x zum Fehler und zeigt auf die Funktion, die angepasst werden muss.

never verschwindet in Unions

never ist die leere Menge von Werten, es einer Union hinzuzufügen ändert also nichts: string | never ist einfach string. So filtern Conditional Types Unions. Ein Zweig, der never zurückgibt, entfernt diesen Member:

Die eingebauten Exclude<T, U> und Extract<T, U> funktionieren genau so. In einer Intersection ist es umgekehrt: string & never ist never.

Unmögliche Typen werden zu never

Eine Intersection, die kein Wert erfüllen kann, wird auf never reduziert:

type A = string & number;                   // never
type B = { kind: "a" } & { kind: "b" };     // never

Das Lesen einer Eigenschaft eines B-Werts meldet den Grund: Property 'kind' does not exist on type 'never'. The intersection 'B' was reduced to 'never' because property 'kind' has conflicting types in some constituents. Wenn ein Typ, den du gebaut hast, sich als never herausstellt, suche nach zwei Teilen, die sich widersprechen.

never, unknown und any

TypEnthaltene WerteZuweisbar anAkzeptiert
unknownjeden Wertnur unknown und anyalles
anyjeden Wertalles außer neveralles
neverkeine Werteallesnur never

unknown ist die Spitze der Typhierarchie und never ihr Boden. any gehört gar nicht zur Hierarchie: Es schaltet die Prüfungen ab.

Häufig gestellte Fragen

Was ist der Typ never in TypeScript?

never ist der Typ, der keine Werte hat. Ihm kann nichts zugewiesen werden (außer einem anderen never), und er ist jedem Typ zuweisbar. Er taucht auf als Rückgabetyp von Funktionen, die immer werfen oder endlos laufen, als Typ einer Variablen, nachdem Narrowing jede Möglichkeit ausgeschlossen hat, und als Ergebnis unmöglicher Typen wie string & number.

Was ist der Unterschied zwischen never und void?

Eine Funktion mit Rückgabe void endet normal; sie gibt nur keinen brauchbaren Wert zurück (zur Laufzeit gibt sie undefined zurück). Eine Funktion mit Rückgabe never endet überhaupt nicht: Sie wirft oder läuft endlos. Code nach dem Aufruf einer never-Funktion ist unerreichbar, und TypeScript behandelt ihn beim Narrowing auch so.

Wie macht man in TypeScript eine Vollständigkeitsprüfung?

Weise im Zweig default eines switch über eine Union den Wert einer Variablen vom Typ never zu oder übergib ihn an eine Funktion assertNever(value: never): never, die wirft. Ist jeder Fall behandelt, ist der Wert dort never, und der Code kompiliert. Fehlt ein Fall, meldet der Compiler, dass der fehlende Member never nicht zuweisbar ist.

Warum ist mein Typ never?

Meist, weil TypeScript jede Möglichkeit weggeengt hat (etwa nach den Prüfungen typeof x === "string" und typeof x === "number" auf einem string | number), oder weil eine Intersection unmöglich ist: string & number oder zwei Objekttypen, deren gemeinsame Eigenschaft widersprüchliche Literaltypen hat. Fahre im Editor mit der Maus über den Typ, um zu sehen, welcher Schritt ihn erzeugt hat.

Was bedeutet "is not assignable to type never"?

Der Code hat versucht, einen echten Wert dort abzulegen, wo nur never erlaubt ist. In einer Vollständigkeitsprüfung bedeutet das, dass ein Union-Member nicht behandelt wurde. Anderswo heißt es oft, dass ein Array als never[] abgeleitet wurde oder eine Intersection zu never zusammengefallen ist; ergänze eine Annotation oder behebe die widersprüchlichen Typen.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S