format_list_bulleted
【TypeScript】try catchをTypeScriptで型安全に用いる方法
最終更新日時:2021-11-05 10:00:17



TypeScriptを用いる理由の一つに型定義をして型安全にコーディングをできる点があると思いますが、trycatch構文では少し型定義に工夫が必要です。本記事ではtrycatch構文を型安全に実装する方法について解説します。

trycatch構文のおさらい

trycatch構文について簡単におさらいをします。trycatch構文についてもう知っている方は飛ばしてください。

try {
    throw new Error('message')
} catch (error) {
    console.log(error)
}

trycatchは上のようにtry句とcatch句に分かれており、try句のなかで呼び出された処理が何らかの例外を発生させた場合、catch句のerrorという引数がこの例外を拾います。

主に、例外の内容はErrorクラスの変数で、catch句では例外として受け取ったerrorを用いて何らかの処理を行います。

catchの引数errorがunknown型になってしまう

TypeScriptではtrycatch構文のcatchの引数(ここではerrorという変数名を割り当てます)はanyまたはunknown型として扱われます。TypeScriptのコンパイル設定によって異なりますが、strictというコンパイル設定をtrueにしている場合はunknown型、falseにしている場合はany型として扱われます。(参考: TypeScriptのコンパイルオプション"strict")

手元のTypeScriptのコンパイルオプションが"strict": falseになっている場合は本記事は参考にならない可能性が高いです。

コンパイルオプションが"strict": trueになっている場合は以下のコードがコンパイル時に引っかかります。

try {
    throw new Error('message')
} catch (error) {
    console.log(error.message)
}

errorというunknown型の変数にmessageというフィールドはないので怒られます。try構文の中ではErrorクラスの例外しか出してないのになぜ?と思う方もいるかもしれませんが以下のような例だと納得するかもしれません。

try {
    const a = Math.random()
    if(a > 0.5){
        throw new Error('message')
    }else{
        throw 'message'
    }
} catch (error) {
    console.log(error.message)
}

tryの中でstring型の'message'という値が例外として投げられる場合も考えると、errorというcatch句の引数がunknown型になることにも納得がいくでしょう。

【解決策】errorの型判定をinstanceof演算子で行う

catch句の中でerrorの型判定を行うことで型安全にerrorを扱うことができます。型判定にはinstanceofというJavaScriptの演算子を用いることができます。(参考: MDNのドキュメント"instanceof")

また、stringなどのクラスではないプリミティブな型についてはtypeof演算子を用いて型判定をすることができます。(参考: MDNのドキュメント"typeof")

try {
    const a = Math.random()
    if(a > 0.5){
        throw new Error('message as error')
    }else{
        throw String('message as string')
    }
    
} catch (error) {
    if(error instanceof Error){
        console.log(error.message) //errorがErrorクラスである場合messageがフィールドに含まれることが保証されるので型安全
    }else if(typeof error === 'string'){
        console.log(error)
    }else{
        console.log("unexpected error")
    }
}

これによって上のようにcatch句の中でerrorの型によって振る舞いを変えることが可能になりました!

catch句でのerrorの型判定について共通化してる部分が大きくなった場合、関数として切り出すことをしたくなりますよね、きっと。その場合、新たな問題が生まれます。その問題について次項で取り上げます。

errorの型判定を関数で共通化するとerrorの型定義がunknown型のままになってしまう

型判定の処理が共通化できたり、複雑化してきた場合、判定の処理をbooleanを返す関数にしたいと考えるのは自然なことですが次のような実装をした場合、問題が生じます。

const hasValueField = (error: any) => {
  if ("value" in error) {
    return true;
  } else {
    return false;
  }
};

try {
  const a = Math.random();
  if (a > 0.5) {
    throw {
      error: new Error("message"),
      value: a,
    };
  }
} catch (error) {
  if (hasValueField(error)) {
    console.log(error.value); // <- errorはunknownなのでvalueというフィールドを呼ぶことが型安全でない
  }
}

コメントでも書いた通り、型判定は通ったとしても、コンパイラには型が安全であると伝わっていないためconsole.log(error.value)のところでコンパイルに指摘されてしまう。

【解決策】is演算子を使って関数の返り値で型を指定する

以下のようにhasValueFieldを修正で解決できます。

const hasValueField = (error: any): error is { value: any } => {
  if ("value" in error) {
    return true;
  } else {
    return false;
  }
};

try {
  const a = Math.random();
  if (a > 0.5) {
    throw {
      error: new Error("message"),
      value: a,
    };
  }
} catch (error) {
  if (hasValueField(error)) {
    console.log(error.value);
  }
}

関数の引数にisという演算子を使ってerrorという引数の型について記述しており、この関数の返り値がtrueであればerrror{ value: any }という型を満たしていること、反対にfalseであれば満たしていないことをコンパイラにも伝えることができます。

この記事のまとめ

本記事ではTypeScriptでのtrycatchを型安全に実装する際のtipsを紹介しました。最後に簡単に内容をまとめておきます。

  • TypeScriptではtrycatch構文のcatch句の引数は(コンパイルオプションで"strict": trueにしている場合)unknown型になる
  • catch句の中でunknown型の型判定を行うことで型安全に例外を扱うことができる
  • unknown型の型判定の際にはinstanceof演算子やtypeof演算子などが活用できる
  • is演算子を用いた関数を型判定に用いることで型判定の処理を関数として共通化することができる