[Node.js] アプリケーションレイヤーでカスタムエラーを定義する「うまみ」が知りたい
0→1の開発を行っているプロジェクトでエラーハンドリングの設計を考え中
Errorインスタンスを拡張したカスタムエラーを使用する方法を紹介している記事が多いけどなんでそうしないといけないんだっけの整理
そもそもなんでカスタムエラーを定義しないといけないんだっけの調査
言語レベルだと、Errorインスタンスを拡張したカスタムエラーを使ったエラー処理は普通にやってることっぽい
例えば、↓はカスタムエラーとしてErrorインスタンスを継承してる
- EvalError
- RangeError
- TypeError
そもそもthrow はいろんな例外を投げれるのにErrorインスタンスを継承したやつじゃないとダメな理由ってなんだ?
throw 文は、ユーザー定義の例外を発生させます。 現在の関数の実行を停止し(throw の後の文は実行されません)、コールスタック内の最初の catch ブロックに制御を移します。
例えば、こんなふうに実装しても動作的にはthrowされた例外がcatchで受け取られるから問題はなさそう
function getRectArea(width, height) {
if (isNaN(width) || isNaN(height)) {
// new Errorではなく、単にオブジェクトをthrowする
throw {errorCode: 10001, message: 'hogehoge'};
}
}
try {
getRectArea(3, "A");
} catch (e) {
console.error(e);
// 実際の出力: Object { errorCode: 10001, message: "hogehoge" }
}
この記事で書かれているように、throwでなんでも投げれるけどErrorインスタンスを通すほうがいいよね(わかりやすいよね)ってことなのか?
JavaScriptでは任意の値を例外としてthrowすることができますが、実際にはErrorのインスタンスをthrowするのが慣例です。
In practice, the exception you throw should always be an Error object or an instance of an Error subclass, such as RangeError. This is because code that catches the error may expect certain properties, such as message, to be present on the caught value. For example, web APIs typically throw DOMException instances, which inherit from Error.prototype.
いや、慣例というよりMDNでも「実際にthrowする例外は、Errorオブジェクトかそれを継承したカスタムエラーであるべき」って書かれてる
かなり前に自分と同じ疑問を持った人が質問してたやつ見つけた
標準規格として定義されたものに則った例外がthrowされてるほうが余計なことを考えなくて済む
Reactではinvariantっていうpackageを使ってErrorオブジェクトをthrowしてるっぽい
throwで投げる例外にはErrorインスタンスを利用すべきとなっているのであれば、カスタムエラーを定義するのは自然な流れなのかも
instance ofでカスタムエラーかどうかを判定して処理を書けることはうまみのひとつ
JavaScript は任意の引数で throw できるので、技術的にはカスタムのエラークラスは Error から継承する必要はありません。しかし、継承しているとエラーオブジェクトを識別する obj instanceof Error を使えるようになります。そのため、継承しておくほうのがベターです。
エラーを分類しつつ実装者が考えないといけないことを減らせる点もうまみポイント
例外をthrowする際はすでに定義されているカスタムエラーを呼び出すだけだから、nameどうしようとかを考える必要がない
当然ながらカスタムプロパティを自由に定義できるのも良き
もしコンストラクタを再定義するときは、cause引数を意識した定義にするのをおすすめします。以下はlocというカスタムプロパティを持つエラークラスを定義する例です。
class ParseError extends Error { static { this.prototype.name = "ParseError"; } constructor(message = "", options = {}) { const { loc, ...rest } = options; // causeがあるときはErrorに渡される super(message, rest); this.loc = loc; } }