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演算子を用いた関数を型判定に用いることで型判定の処理を関数として共通化することができる