created: 2022-08-02T09:21:07.084Z

TypeScript でカスタム例外クラスを実装する

MDNだとこんなコード例が載っている。

class CustomError extends Error {
  constructor(foo = 'bar', ...params) {
    // 一部割愛
    super(...params)
    ...
    this.name = 'CustomError'
    ...
  }
}

基本的にはこれでいいのだが、TypeScript ではこれだけだと instanceof が正しく動作しない。

つまり以下のコードが期待通り動作しない場合がある。

try {
  ...
} catch (e) {
  if (e instanceof CustomError) {
    ...
  }
}

これは TypeScript の Wiki にも書いてある。

instanceof will be broken between instances of the subclass and their instances, so (new FooError()) instanceof FooError will return false.

「場合がある」と書いたのは、どのバージョンの ECMAScript にコンパイルするかによって動作しないから。 ES 2015 以降の class 構文が入っているスクリプトにコンパイルする場合は↑のコードは期待通りに動作する。

ワークアラウンド

instanceof を動作させるためには以下の1行を入れる必要がある。

Object.setPrototypeOf(this, CustomError.prototype);

class CustomError extends Error {
  constructor(foo = 'bar', ...params) {
    // 一部割愛
    super(...params)
    ...
    this.name = 'CustomError'
    Object.setPrototypeOf(this, CustomError.prototype);
    ...
  }
}

その他

自分の場合は webpack の modedevelopment から production に切り替えるところでこの挙動にあたった。 mode=production だと tsconfig#target が勝手に変わる設定になっていたため、開発中は instanceof が動作するけど、本番で動作しないということが起こった。

参考

サイバーセキュリティの教科書
[ad] サイバーセキュリティの教科書
Thomas Kranz, Smoky (単行本(ソフトカバー))