Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: ErrorCauseへの対応 #1732

Merged
merged 19 commits into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions source/basic/error-try-catch/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,52 @@ MDNの[JavaScriptエラーリファレンス][]には、ブラウザが投げる
また、ほとんどのブラウザには`console.log`や`console.error`の出力をフィルタリングできる機能が備わっています。
ただのログ出力には`console.log`を使い、エラーに関するログ出力には`console.error`を使うことで、ログの重要度が区別しやすくなります。

## [ES2022] Error Cause {#error-cause}

エラーをキャッチし新しいメッセージや別の `Error` オブジェクトで送出すると、デバッグに有益な情報をエラーに付与することできて便利です。
azu marked this conversation as resolved.
Show resolved Hide resolved
これを実現する時に、新しく `Error` オブジェクトを作成して `throw` すると元のエラーのスタックトレースが失われてしまいます。
azu marked this conversation as resolved.
Show resolved Hide resolved
himanoa marked this conversation as resolved.
Show resolved Hide resolved

この問題を解決するには、`catch` 句で補足した変換元の例外を、新しい `Error` オブジェクトのコンストラクタに渡すことで、変換元のエラーのスタックトレースを保持することができます。
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

このスタックトレースが失われる問題を解決するには、ES2022で追加されたErrorの`cause`オプションが利用できます。
新しいエラーオブジェクトを作成する際に、第2引数の`cause`オプションに元々?のエラーオブジェクトを渡すことで、のスタックトレースを保持できます。

という感じですかね。
Original Errorというのを何というかちょっと難しい。。

  • 元々のエラー
  • キャッチしたエラー
  • 補足したエラー

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

補足したエラーかキャッチしたエラーが、指している対象が明確になっていて良いと感じました。

このファイルの一貫性重視でキャッチしたエラーを使おうと思います

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

散らかすようですが「本来のエラー」という言い方もできそうです。

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

本来のエラーは結構しっくり来るので、その方針にしようとおもいます!


{{book.console}}
himanoa marked this conversation as resolved.
Show resolved Hide resolved
```js
// 数字の文字列を二つ受け取り、合計を返す関数
function sumNumStrings(a, b) {
try {
const aNumber = safeParseInt(a);
const bNumber = safeParseInt(b);
return aNumber + bNumber;
} catch (e) {
throw new Error("Failed to sum a and b", { cause: e });
}
}

// 数値の文字列を受け取り数値を返す関数
// 'text' など数値にはならない文字列を渡された場合は例外を送出する
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// 'text' など数値にはならない文字列を渡された場合は例外を送出する
// 'text' など数値にはならない文字列を渡された場合は例外を投げる

function safeParseInt(numStr) {
const num = parseInt(numStr, 10);
if (Number.isNaN(num)) {
throw new Error(`${numStr} is not a numeric`);
}
return num;
}

try {
// 数値にならない文字列 'string' を渡しているので例外が送出される
azu marked this conversation as resolved.
Show resolved Hide resolved
sumNumStrings("string", "2");
} catch (err) {
console.error(`エラーが発生しました (${err})`);
// `cause` プロパティを参照することで、throwする時に `cause` で渡したエラーにアクセスすることができる
himanoa marked this conversation as resolved.
Show resolved Hide resolved
console.error(`詳細 (${err.cause})`);
Copy link
Collaborator

@azu azu Mar 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

エラーの発生元?とかですかね? ( 実はcauseはなんでも入れられるのでなんでもアリだと思いますが)

Suggested change
console.error(`詳細 (${err.cause})`);
console.error(`エラーの発生元 (${err.cause})`);

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

発生元というとどこで投げられたのかという「場所」のイメージをもたせそうですがそれはスタックトレースの情報なので、「原因」のニュアンスが与えられるほうがいいんじゃないかと思います。

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(.causeはオプショナルなので、全てのエラーに存在するわけじゃないため、何かしら判定して出すかブラウザ側に任せるのが正しい気はします)
#1732 (comment)

の観点を考えると L320-L321丸々削ってしまってもいいのではないかと思いました。

}
```
azu marked this conversation as resolved.
Show resolved Hide resolved

このスクリプトを読み込むと、`sumNumsStrings` の例外に `safeParseInt` から投げられたスタックトレースが付与された状態のエラーログがコンソールに出力されます。
ここではFirefoxにおける実行例を示します。


![safeParseIntのスタックトレースが含まれたconsole.errorの出力結果](./img/error-cause.png)

## まとめ {#conclusion}

この章では、例外処理とエラーオブジェクトについて学びました。
Expand All @@ -291,6 +337,7 @@ MDNの[JavaScriptエラーリファレンス][]には、ブラウザが投げる
- `throw`文は例外を投げることができ、`Error`オブジェクトを例外として投げる
- `Error`オブジェクトには、ECMAScript仕様や実行環境で定義されたビルトインエラーがある
- `Error`オブジェクトには、スタックトレースが記録され、デバッグに役立てられる
- Error cause を使うことで例外を変換して送出する際に元のスタックトレースを保持できる
azu marked this conversation as resolved.
Show resolved Hide resolved

[try...catch]: https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/try...catch
[throw]: https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/throw
Expand Down
Binary file added source/basic/error-try-catch/img/error-cause.png
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 スクリーンショットは、後でここにまとめて撮り直す。

https://github.com/asciidwango/js-primer/blob/master/source/basic/error-try-catch/screenshot-manual.sh

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

このシェルスクリプトは僕の環境で実行してコミットにスクリーンショット付ける形で大丈夫ですか?

Copy link
Contributor Author

@himanoa himanoa Mar 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

これ、手元にmacOSマシンがなくて実行できなさそうでした 🙇

Copy link
Collaborator

@azu azu Mar 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

スクショは統一感が大事だと思うので(OSでUIが異なる)、一旦そのままで大丈夫ですー

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions source/use-case/ajaxapp/promise/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ function main() {
.catch((error) => {
// Promiseチェーンの中で発生したエラーを受け取る
console.error(`エラーが発生しました (${error})`);
console.error(`詳細 (${error.cause})`);
Copy link
Collaborator

@azu azu Mar 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

error 自体のログにcauseを含むスタックトレースが入ってるので、明示的にcauseを参照する必要性はないかなと思います。
エラーキャッチする側が.causeが存在しているかを知ってるのはおかしいと思うので、console.error(error.cause) と書くケースは実際にはないかなとは思っています。
(.causeはオプショナルなので、全てのエラーに存在するわけじゃないため、何かしら判定して出すかブラウザ側に任せるのが正しい気はします)


image

Cause is displayed in console
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause

まだFirefoxだけだったのか。。(意外)
Node.jsは独自で対応してたのか。

ChromeもCanary版ではサポートしてたので、.causeは省略していいかなーとは思いました。

image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

承知しました!削除します

});
}

Expand All @@ -100,6 +101,9 @@ function fetchUserInfo(userId) {
displayView(view);
});
}
})
.catch(err => {
return Promise.reject(new Error(`Failed fetch user(id: ${userId}) info`, { cause: err }));
});
}
```
Expand Down Expand Up @@ -139,6 +143,7 @@ function main() {
// Promiseチェーンでエラーがあった場合はキャッチされる
.catch((error) => {
console.error(`エラーが発生しました (${error})`);
console.error(`詳細 (${error.cause})`);
azu marked this conversation as resolved.
Show resolved Hide resolved
});
}

Expand All @@ -151,6 +156,9 @@ function fetchUserInfo(userId) {
// JSONオブジェクトで解決されるPromiseを返す
return response.json();
}
})
.catch(err => {
return Promise.reject(new Error(`Failed fetch user(id: ${userId}) info`, { cause: err }));
});
}
```
Expand All @@ -177,6 +185,7 @@ async function main() {
displayView(view);
} catch (error) {
console.error(`エラーが発生しました (${error})`);
console.error(`詳細 (${error.cause})`);
azu marked this conversation as resolved.
Show resolved Hide resolved
}
}
```
Expand Down
Loading