JavaScriptで作ったアプリでのエラーをハンドリングしたい
例えば、Node.jsのAPIサーバーでリクエストパラメータの精査エラーをハンドリングするなど
特定のエラーを処理する場合はMDNにあるようにinstanceof
で判別することができる
try { foo.bar() } catch (e) { if (e instanceof EvalError) { console.error(e.name + ': ' + e.message) } else if (e instanceof RangeError) { console.error(e.name + ': ' + e.message) } // ... etc }
なので、 Errorを継承したカスタムエラークラスを作ればよいかと思ったが、ちょっと知らないことがあったのでメモ
単純に継承する場合は以下のようにする
class ValidationError extends Error { constructor(message) { super(message); this.name = "ValidationError"; } } function run() { throw new ValidationError("エラーメッセージ"); } try { run(); } catch(err) { console.log(err); }
これで、ちゃんとinstanceof
で判別できるし、スタックトレースも期待通り表示される
ValidationError: エラーメッセージ at run (/Users/uga/playground/error-handing/test.js:9:9) at Object.<anonymous> (/Users/uga/playground/error-handing/test.js:13:3) at Module._compile (node:internal/modules/cjs/loader:1092:14) at Object.Module._extensions..js (node:internal/modules/cjs/loader:1121:10) at Module.load (node:internal/modules/cjs/loader:972:32) at Function.Module._load (node:internal/modules/cjs/loader:813:14) at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:76:12) at node:internal/main/run_main_module:17:47
こんな要領でValidationError
よりもうちょっと詳細な、必須エラー(RequiredError)や最大文字数エラー(MaxLengthError)などを作くりたくなって増やしたのだが、
class RequiredError extends Error { constructor(props) { super(`"${props}"は必須項目です。`); this.name = "RequiredError"; this.props = props; } } class MaxLengthError extends Error { constructor(props) { super(`"${props}"は10文字以上にしてください。`); this.name = "MaxLengthError"; this.props = props; } }
this.name = "XXXXError";
のところが毎回手書きで、スペルミスに注意したりして手間だと感じた
そんなとき、コンストラクタでthis.name = this.constructor.name;
としておくとクラス名がname
にセットされることがわかった
class RequiredError extends Error { constructor(props) { super(`"${props}"は必須項目です。`); this.name = this.constructor.name; this.props = props; } }
とすれば良いが、それすらも手間なので、ベースとなるカスタムクラスを作って継承させるとスッキリする
class BaseError extends Error { constructor(message) { super(message); this.name = this.constructor.name; } } class RequiredError extends BaseError { constructor(props) { super(`"${props}"は必須項目です。`); this.props = props; } } class MaxLengthError extends BaseError { constructor(props) { super(`"${props}"は10文字以上にしてください。`); this.props = props; } }
次に、これらのカスタムエラー以外のSyntaxError
やReferenceError
、TypeError
などを捉えて、共通的なメッセージを表示させたい
例えば、以下のようにrun
関数を定義した場合、(1)でTypeError
が発生するが、スタックトレースには任意のメッセージは入れられない
function run(name) { if (!name) { throw new RequiredError("name"); } const firstKanjiName = name.first.kanji // --------(1) } try { run("uga"); } catch(err) { console.log(err); }
Errorを捕らえるために、run
関数でtry-catch
を行い、新たなRunError
をつくることにした
function run(name) { try { if (!name) { throw new RequiredError("name"); } const firstKanjiName = name.first.kanji // --------(1) } catch (error) { throw new RunError("run実行中にエラーが発生しました。", error); } }
RunError
は以下のように実装しておく
class RunError extends BaseError { constructor(message, cause) { super(message); this.cause = cause; } }
これで、(1)でTypeError
を発生させると以下のような形でスタックトレースに表示された
RunError: run実行中にエラーが発生しました。 at run (/Users/uga/playground/error-handing/test.js:38:11) at Object.<anonymous> (/Users/uga/playground/error-handing/test.js:43:3) at Module._compile (node:internal/modules/cjs/loader:1092:14) at Object.Module._extensions..js (node:internal/modules/cjs/loader:1121:10) at Module.load (node:internal/modules/cjs/loader:972:32) at Function.Module._load (node:internal/modules/cjs/loader:813:14) at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:76:12) at node:internal/main/run_main_module:17:47 { cause: TypeError: Cannot read property 'kanji' of undefined at run (/Users/uga/playground/error-handing/test.js:36:39) at Object.<anonymous> (/Users/uga/playground/error-handing/test.js:43:3) at Module._compile (node:internal/modules/cjs/loader:1092:14) at Object.Module._extensions..js (node:internal/modules/cjs/loader:1121:10) at Module.load (node:internal/modules/cjs/loader:972:32) at Function.Module._load (node:internal/modules/cjs/loader:813:14) at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:76:12) at node:internal/main/run_main_module:17:47
これで、原因追跡のための情報を仕込んだりして調査を楽にすることができそう