14
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Error の cause オプションによってエラーの再 throw 時にスタックトレースが失われるのを防ぐ【ES2022】

Last updated at Posted at 2022-04-24

「エラーをキャッチして再度エラーを投げる」をやる場合、以下のように書くと元のエラー情報が失われてしまうためデバッグしづらいコードになります。

try {
    const user = await fetch(userApiUrl);
} catch (error) {
    throw new Error("ユーザー情報取得APIでエラー");
}

メッセージに error.messageerror.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 も確認できます。
cause1.png

もちろん、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);
}

cause2.png

なお、 cause 以外のプロパティ名で渡しても何も渡されません。

ブラウザの対応状況

この機能は Chrome 93, Firefox 91 で対応 されており、去年の 8 月あたりから使えるようになっています。
Node.js は 16.9.0、 Deno は 1.13 からですね。

また can-i-use を見ると、2022/4/24 現在はユーザー全体の 81% は対応したブラウザを使っているようです。
can-i-use.png

プロポーザルにも記載されているように 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 も用意されているので使ってみてはいかがでしょうか。

14
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?