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
void | never | |
|---|---|---|
| Funktion endet | ja | nein (wirft oder läuft endlos) |
| Wert zur Laufzeit | undefined | keiner: Der Aufruf erzeugt nie einen |
| Code nach dem Aufruf | erreichbar | unerreichbar |
| Anderen Typen zuweisbar | nur void, unknown, any | jedem Typ |
| Typische Verwendung | Callbacks, Event-Handler, Funktionen mit Nebeneffekten | fail(), 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
| Typ | Enthaltene Werte | Zuweisbar an | Akzeptiert |
|---|---|---|---|
unknown | jeden Wert | nur unknown und any | alles |
any | jeden Wert | alles außer never | alles |
never | keine Werte | alles | nur 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.