「エラーをキャッチして再度エラーを投げる」をやる場合、以下のように書くと元のエラー情報が失われてしまうためデバッグしづらいコードになります。
try {
const user = await fetch(userApiUrl);
} catch (error) {
throw new Error("ユーザー情報取得APIでエラー");
}
メッセージに error.message
や error.trace
を連結して入れるのも手ですが、メッセージとしては読みづらく使いづらいです。
よってこれまでは、コード全体のエントリポイントなどでまとめてエラーをキャッチしスタックトレースが失われない形で処理するなどしていたかと思います。
Error の cause オプション
ES2022 で入った機能で、このように Error
の第二引数にオプションで cause
を渡せるようになりました。
throw new Error("エラー", {
// ここに前のエラーを渡せる
cause: previousError,
});
実際に Chrome DevTools で以下を実行してみると、
try {
try {
throw new Error("エラーA");
} catch (error) {
throw new Error("エラーB", { cause: error });
}
} catch (error) {
console.dir(error);
}
エラー B の cause
にエラー A が入っており、 stack
も確認できます。
もちろん、2 個以上の連鎖も可能です。
try {
try {
try {
throw new Error("エラーA");
} catch (error) {
throw new Error("エラーB", { cause: error });
}
} catch (error) {
throw new Error("エラーC", { cause: error });
}
} catch (error) {
console.dir(error);
}
なお、 cause
以外のプロパティ名で渡しても何も渡されません。
ブラウザの対応状況
この機能は Chrome 93, Firefox 91 で対応 されており、去年の 8 月あたりから使えるようになっています。
Node.js は 16.9.0、 Deno は 1.13 からですね。
また can-i-use を見ると、2022/4/24 現在はユーザー全体の 81% は対応したブラウザを使っているようです。
プロポーザルにも記載されているように Polyfill もすでにあり、また core-js も こちらの PR で対応済みです。
なにが嬉しいか
エラーの再 throw 時にスタックトレースが失われないため、コードの小さい単位でエラーをキャッチしてより詳細なメッセージを投げれるようになり、デバッグなどがやりやすくなるかと思います。
途中で別物の例外に変えることにはなるので、そのまま上位のコードにハンドリングを任せたほうがいい場合もあると思いますが、エラーのハンドリングに関して JS の標準機能で取れる選択肢が増えたということで設計の幅も広がりそうです。
余談
少し話は変わりますが、Promise のエラーハンドリングで await-to-js というのが便利で、Go 言語ライクにエラーハンドリングができます。
このように通信の処理を書くとすると
try {
const user = await fetchUser();
} catch (error) {
throw new Error("ユーザー情報取得APIでエラー", { cause: error });
}
await-to-js を使うと、エラーを処理する前提で書けるようになります。
const [error, user] = await to(fetchUser());
if (error) {
throw new Error("ユーザー情報取得APIでエラー", { cause: error });
}
通信周りのエラーハンドリングを毎回その場で処理するようになった場合は、ハンドリング処理を書き忘れづらくなるので便利です。
TypeScript にも対応してます。
さいごに
PHP などを触られている方は Exception にエラー情報を渡せるのですでに馴染みがあるかもしれません。
JS でも同様のことができるようになり、エラーハンドリング周りの設計でまた別の選択肢が取れるようになりました。
Polyfill も用意されているので使ってみてはいかがでしょうか。