Menu

TypeScriptのtry catch: エラーの型と独自のエラー

TypeScriptのエラー処理を解説します。catch の変数が unknown になる理由、instanceof Error での絞り込み、エラーの throw、name と cause を持つ独自のエラークラスの書き方、そして想定されるエラーのための Result 型パターンです。

このページのコードはエディタで実行できます - 編集してすぐに結果を確認できます。

TypeScriptはJavaScriptの try、catch、finally、throw を使います。TypeScript特有なのは catch の変数です。strict が有効なら型は unknown なので、使う前に何が throw されたかをチェックします。

実行時の動き(スタックがどう巻き戻るか、どんな組み込みのエラー型があるか)は JavaScriptのtry/catch で説明しています。

catch の変数が unknown になる理由

JavaScriptは何でも throw できます。Error、文字列、数値、undefined、ライブラリのオブジェクトなどです。TypeScriptにはどれかわからないので、strict(useUnknownInCatchVariables フラグ)のもとでは変数は unknown になり、使う前に絞り込む必要があります:

index.ts(5,34): error TS18046: 'e' is of type 'unknown'.

strict がなければ e は any で、同じコードはコンパイルできますが、文字列が throw されると実行時におかしくなります。e.message は undefined になり、e.message.length のようにさらに奥を読むと TypeError が投げられます。型注釈で逃げることもできません。catch (e: Error) はエラー TS1196(Catch clause variable type annotation must be 'any' or 'unknown' if specified)です。

エラーを絞り込む

instanceof Error は、TypeError、SyntaxError、RangeError、自作のサブクラスなど、Error をもとにしたものすべてをカバーします。それ以外は値を文字列に変換して扱います。小さなヘルパーを作れば、呼び出し箇所を短く保てます:

一部のエラーだけを処理する catch は、残りを投げ直すべきです: if (!(e instanceof ValidationError)) throw e;。未知のエラーを握りつぶすと、本当のバグが隠れてしまいます。

エラーを throw する

throw はどんな式でも受け付け、TypeScriptも制限しません。それでも Error オブジェクトを throw しましょう。スタックトレースを持ち、コードベースのあらゆる instanceof Error のチェックがそれに依存しています。

常に throw する関数の戻り値の型は never で、TypeScriptは呼び出しのあとの絞り込みにこれを使います:

ES2022 以降、Error は cause を持つ2番目の引数を受け取り、低レベルのエラーを自分が throw するエラーにつなげられます: throw new Error("could not load settings", { cause: e })。呼び出し側は err.cause(型は unknown)を読めます。

独自のエラークラス

Error のサブクラスを作ると、呼び出し側が instanceof で失敗を区別でき、追加のデータも持たせられます。継承した name は "Error" で、ログや String(err) に表れるので、name を設定しましょう:

NotFoundError は HttpError でもあるので、いちばん具体的なクラスを先にチェックします。noImplicitOverride が有効でなければ、ここでの override 修飾子は任意です。

古いガイドでは、すべてのエラーのコンストラクターに Object.setPrototypeOf(this, new.target.prototype) を追加しています。これはクラスを ES5 の関数にコンパイルしていたときに、Error のサブクラスで instanceof が壊れたために必要だったものです。TypeScript 7 は target: "es5" をもうサポートしておらず(TS5108: Option 'target=ES5' has been removed が報告されます)、ES2015 以降では上の例のようにネイティブの class ... extends Error がそのまま動きます。

Result パターン

TypeScriptは関数がどのエラーを throw するかを追跡しないので、呼び出し側に処理を促すものは何もありません。想定される失敗(不正な入力、存在しないレコード)には、「成功か失敗か」を表す値を返すと、エラーが型システムに組み込まれます:

r.ok をチェックする前に r.value を読むとコンパイルエラーになります。それこそが狙いです。想定外のもの(バグ、切れた接続)には例外を、呼び出し側が判断しなければならない結果には Result の値を使いましょう。

非同期コードのエラー

reject された Promise は await の位置で throw されたエラーになるので、await を囲む try/catch も同じように動き、変数はやはり unknown です。唯一の違いは Promise の .catch() で、そのコールバックの引数は unknown ではなく any と型付けされているので、自分で型注釈を付けます(.catch((e: unknown) => ...))。例は async/await を参照してください。

よくある間違い

  • 絞り込まずに e.message を読む。 strict のもとではコンパイルできず、strict なしでは Error 以外が throw されたときに壊れます。
  • すべてを捕まえて処理を続ける。 想定しているエラーを処理し、残りは投げ直しましょう。
  • 独自のエラーに name を付け忘れる。 ログではどれも Error と表示されてしまいます。
  • 文字列を throw する。 throw "failed" にはスタックトレースがなく、instanceof Error のチェックにも失敗します。

よくある質問

TypeScriptの catch ブロックのエラーは何型ですか?

strict が有効(useUnknownInCatchVariables フラグ)なら unknown、そうでなければ any です。JavaScriptは Error オブジェクトだけでなくどんな値でも throw できるので、TypeScriptはチェックを求めます。e.message を読む前に if (e instanceof Error) で絞り込みましょう。

TypeScriptで catch の変数を Error 型にできますか?

できません。catch (e: Error) はエラー TS1196: Catch clause variable type annotation must be 'any' or 'unknown' if specified です。何が throw されたかは何も保証されないので、許されるのは unknown と any だけです。代わりにブロックの中で絞り込みます。

TypeScriptでエラーを throw するには?

JavaScriptと同じです: throw new Error("message")、または new RangeError(...) のような組み込みや独自のサブクラスのインスタンスを throw します。TypeScriptはどんな値でも throw させてくれますが、Error オブジェクトを throw すればスタックトレースが残り、instanceof Error のチェックも動きます。

TypeScriptで独自のエラークラスを作るには?

Error を継承し、super(message, options) を呼び、name を設定します: class NotFoundError extends Error { name = "NotFoundError"; }。追加のフィールドはコンストラクターに書きます。サポートされているどのターゲット(ES2015 以降)でも、プロトタイプの修正なしで instanceof NotFoundError が動きます。

TypeScriptに検査例外や throws 節はありますか?

ありません。関数の型は、何を throw するかについて何も表さず、コンパイラーはエラーが処理されているかを一切チェックしません。呼び出し側に処理してほしいエラーは、Result 型(成功と失敗の判別可能なユニオン型)の値として返せば、型システムがチェックしてくれます。

Coddy programming languages illustration

Coddyでコードを学ぼう

始める