TypeScriptのtry/catchを用いたError Handling
良い(?)点
JS/TSの言語思想に則ったError Handlingと言える
標準libraryや、多くのthrid party libdaryはthrowする
返り値の型は正常系のみになる
良くない点
どの関数がthrowするのかが型から判別できない
JSON.parse()初見の人は、docsか内部実装を読む以外に「これは失敗時にthrowする」ということを知る術はない
厳密に調べるには、内部実装を全部見る必要がある
catch節内でErrorの種類の分岐ができるが、種類数が自明でない
上で書いた問題と同じ問題
どの関数が何のErrorを返すのか知るためには、内部実装を全部見るしかない
同様に、過剰に分岐しすぎる可能性もある
HogeErrorは絶対にここ到達しないのにif (e instance HogeError)と書いている、みたいなことが起きる
しかも安易に消せない
これは、catchしている関数の責務が明示されていればさほど問題にならないmrsekut.icon
Error Handlingを強制できない
利用者が気付かずに、try節の中で書かなければ、一番上までthrowされ、落ちる
try/catchをどう書くか
code:ts
try {
...
} catch (e) { // eの型はunknown
if (e instanceof NotFound) {
console.error(e.message);
..
} else {
throw e;
}
} finally { .. }
つまり、tryの中でthrowされるErrorを想定している必要がある
ここは型からわからないので、内部実装を全て読む以外に知る術はない
最後のelse節に入るものは、想定できなかったErrorなので、
再throwして呼び出し元に返すか
何かしらのhandlingをするか
揉み消すか
をすることになる
そのため、条件分岐が強制されるため、無効(e: any)の時よりは安全になる
finally節は、try/catchいずれが実行されも最後に呼ばれる
Custom Errorをどう作るか
Custom Errorとは何で、なぜ必要か
Custom Errorとは、JS標準のErrorclassを継承したclassのことを指す
stackがうんたら
原理的にはtagged unionでもできるはず
stackのため、
拡張性(?)のため、
instanceofを使うということは、階層構造が存在することを暗に想定している感じがする
instanceOfというか普通にkeyで弁別すれば良いのでは #?? なんでわざわざclassを作る?なんかほしいmethodがあるのか?
Errorオブジェクトを使うことでstackを積み上げることができるから
それでもthrow myObjectとかでも一応できる
プロダクト内で一切Errorを使わない感じ
code:ts
try {
throw {
key: "PermissionError",
message: "パーミッションエラーです",
};
} catch (err) {
if (err.key === "PermissionError") {
console.log(err.message);
}
}
依然として型安全ではない
これはstackが積み上がらないのでネストされた例外のときに追跡が難しくなる
継承を重ねて拡張される可能性があるから
Errorのinterface
code:ts
interface Error {
name: string; // クラス名
message: string; // new Error('ここ')
stack?: string; // スタックトレース
}
同一のpropertyを持った複数のclassを分岐させるには継承とかしかないんか?
class以外もthrowできるが、ライブラリの内部でthrowされたものはたいていclassだろうから、classに合わせたほうが良いかもしれん
どう作るか?
どこまで値をもたせるか?のような話は、必要になった時に付け足せばいいので、最小限の構成と、拡張例を1つぐらいメモっておけばだいたい応用できそうmrsekut.icon
欲しい情報
メッセージ
起きた場所
catchした場所ではなく、throwした関数の名前とか欲しい
それは普通にスタックトレース見れば出てるはずmrsekut.icon
カスタムエラーclassを作るときのコツ
普通はErrorクラスを継承したカスタムクラスを作る
理由は後述
カスタムクラスは定義したいエラーの種類数作る必要がある
↑これらの処理は基本的な部分は大抵同じになるのでルート的なカスタムエラークラスを一つ作っていると良い
code:こんな感じ
Error // 標準で用意されている
BaseError // ルート的なもの. Errorを継承している
NoNetWorkError // ネットワークエラー用。 BaseErrorを継承
NetworkAccessError // アクセスエラー用. BaseErrorを継承
カスタムエラークラスを作る意味
catch節でinstanceofで条件分岐させたいから
catchの中でやる分岐は、
自分で作ったerror、
それ以外、
の両方を書いていないと安全でない
「それ以外」の方では、再びnew throwするのがいい
開発者向けのエラーメッセージと、Viewにユーザー向けに表示するエラーメッセージを便利に両方指定できるようにしたい
TypeScriptの標準の例外クラス
JSONパース時に構文がおかしいとき
fetch時にネットワークエラーが起きたとき
code:ts
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
}
}
name
何のため?
自作例外クラスを作る場合
Errorをextendsしたclassを作る
スタックトレースを利用できる
Errorをimplementしたclassを作る
スタックトレースを利用できない
カスタムエラーの例
code:ts
abstract class BaseError extends Error {
constructor(m: string) {
super(m);
}
}
export class ImgUploadError extends BaseError {
constructor(errorMessage: string) {
super(errorMessage);
}
}
export class BarError extends BaseError {
constructor(errorMessage: string) {
super(errorMessage);
}
}
code:使用例.ts
try {
//
} catch (err) {
if (err instanceof PermissionError) { .. }
if (err instanceof UserCreateError) { .. }
}
カスタムエラークラスでErrrorを継承しているものと実装しているものとの差
Interfaceの実装のみなので、stackが積まれない
stackも自身で実装する必要が出てくる
teratail舐めてたけど、結構良かったmrsekut.icon
type safe
条件分岐できるようになった