1必ず throw する関数は never
never の最も基本の形です。必ず例外を投げる関数は「正常に値を返して戻ることが決してない」ので、戻り値の型が never になります。try / catch で受け止めて、値を返さずに抜けたことを確かめましょう。
// 必ず throw するので、値を返して戻ることが決してない
function crash(reason: string): never {
throw new Error(reason);
}
try {
crash("設定ファイルが壊れています");
// ここに到達することはない
} catch {
console.log("crash は値を返さず、例外で抜けた");
}
console.log("catch のおかげでプログラムは続行できる");
2void と never の違い
void は「値を返さずに普通に終わって戻ってくる」、never は「決して戻ってこない」です。並べて動かすと、notify の次の行は実行されるのに abortProcess の後ろには進まない、という違いがはっきり分かります。
// void: 値は返さないが、普通に終わって戻ってくる
function notify(message: string): void {
console.log("通知: " + message);
}
// never: throw して決して戻ってこない
function abortProcess(message: string): never {
throw new Error(message);
}
notify("保存しました");
console.log("notify の後は普通に続く");
try {
abortProcess("致命的なエラー");
} catch {
console.log("abortProcess は戻らず、catch に飛んだ");
}
3never の関数を呼んだ先には進まない
戻り値が never の関数を呼ぶと、コンパイラは「この先には到達しない」と理解します。number を返すはずの関数の最後が failWith で終わっていても return 漏れと扱われないのは、その理解があるからです。エラー処理を1か所にまとめる実務パターンです。
function failWith(message: string): never {
throw new Error(message);
}
function planPrice(planName: string): number {
if (planName === "basic") {
return 980;
}
if (planName === "pro") {
return 2980;
}
failWith("未知のプラン: " + planName);
// failWith は never なので、ここに来ないとコンパイラも理解している
}
console.log(planPrice("basic") + "円");
console.log(planPrice("pro") + "円");
4絞り込みの行き着く先は never
ユニオン型の可能性を絞り込みですべて消すと、残った型は never になります。「もうどの値でもあり得ない」状態を表すのが never だ、という見方ができる例で、次の網羅性チェックの土台になります。
function describeId(rawId: string | number): string {
if (typeof rawId === "string") {
return "文字列ID: " + rawId.toUpperCase();
}
if (typeof rawId === "number") {
return "数値ID: " + rawId.toFixed(0);
}
// string と number を両方消したので、ここでの rawId は never 型
return rawId; // never はどの型にも代入できるため、これが通る
}
console.log(describeId("abc-001"));
console.log(describeId(42));
5switch の網羅性チェック
判別可能ユニオンを switch で分岐し、default で never 型の変数に代入するのが網羅性チェックの定番です。将来ステータスの種類が増えて分岐が漏れると、default に never でない型が流れ込んでコンパイルエラーになり、修正漏れに必ず気づけます。
type OrderState =
| { kind: "pending" }
| { kind: "shipped"; trackingCode: string }
| { kind: "delivered"; dateText: string };
function describeOrder(order: OrderState): string {
switch (order.kind) {
case "pending": return "発送準備中";
case "shipped": return "配送中: " + order.trackingCode;
case "delivered": return order.dateText + " に配達済み";
default: {
const unhandled: never = order; // 分岐漏れがあるとこの行がエラーになる
return unhandled;
}
}
}
console.log(describeOrder({ kind: "pending" }));
console.log(describeOrder({ kind: "shipped", trackingCode: "JP-1234" }));
console.log(describeOrder({ kind: "delivered", dateText: "6月10日" }));