非同期のエラーを捕まえる
このレッスンで分かること
- 失敗した Promise を
awaitすると、その行で例外が投げられますawaitを書き忘れると、tryで囲んでも失敗をすり抜けます
catch を書いたのに、失敗がすり抜ける
非同期の失敗でいちばん多い事故は、try の書き方ではなく await の書き忘れです。下のコードは try で囲んであるのに、catch はいつまでも動きません。
JavaScript
async function readBio(profileId) {
try {
const bio = fetchBio(profileId); // await が無い
return bio.text;
} catch (error) {
return "自己紹介はまだありません";
}
}await が無いので bio に入るのは箱そのものです。bio.text は undefined になり、そのまま return まで進みます。箱が失敗の返事を出すのはそのあとで、try はもう抜けたあとです。失敗はどこにも受け止められず、画面には何も起きず、原因も分からないという最悪の形になります。
同じことが return fetchBio(profileId); でも起きます。try の中に書いてあっても、箱をそのまま返しているので、失敗するのは呼ばれた側を抜けたあとです。try の中で受け止めたいなら return await と書きます。
失敗という返事を作る
Promise の返事は 2 通りでした。成功が resolve、失敗が reject です。reject に渡すのは Error のオブジェクトにします。
JavaScript
function fetchBio(profileId) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
if (!profileId) {
reject(new Error("プロフィールが指定されていません"));
return;
}
resolve("Web エンジニアを目指しています");
}, 80);
});
}reject を呼んだあとの return に注目してください。reject は関数から抜ける構文ではないので、書かないと下の resolve まで届いてしまいます。確定した箱は動かないので実害は出ませんが、読む人には迷子の行に見えます。
| 起きたこと | await した行の動き | どこで受けるか |
|---|---|---|
resolve された | 中身が変数に入る | 次の行へ進む |
reject された | 例外が投げられる | 同じ try の catch |
| どちらも呼ばれない | 再開しない | どこでも受けられない |
3 行目が非同期ならではの落とし穴です。確定しない箱は失敗ですらありません。エラーも出ないまま関数が止まったままになり、採点では時間切れとして扱われます。
失敗しても、同じ形の値を返す
reject された箱を await すると、その行で例外が投げられます。同期の throw とまったく同じ扱いなので、受け止め方も同じです。
JavaScript
async function readBio(profileId) {
try {
return await fetchBio(profileId);
} catch (error) {
return "自己紹介はまだありません";
}
}catch が受け取る error は、reject に渡した Error オブジェクトそのものです。error.message で文章を取り出せます。
ここで大事なのは、成功しても失敗しても同じ形の値を返しているところです。片方は文字列、片方は undefined という関数にすると、呼ぶ側に「どちらだったか」を確かめる分岐が増えます。失敗の面倒を見た関数が、外へ面倒を持ち出さない形にしておいてください。
new Promiseを書かず、async functionの中で直接throwすることもできます。投げた例外は、その関数が返す Promise の失敗としてそのまま外へ出るので、呼ぶ側から見ればrejectされたときと同じです。
失敗を作る側と、受け止める側を分ける
上の fetchBio の例では、指定が無いと reject が失敗の返事を作り、readBio の await でその返事が例外になります。演習では、作品を探す処理とタイマーと reject を補助関数に用意してあります。まずその関数を読んで成功と失敗を確認し、自分では受け止める側の try、await、catch を書きます。
Error が持つ文章だけを取り出す小さな例を確認しましょう。
JavaScript
const reason = new Error("画像を開けません");
console.log(reason.message); // 画像を開けませんreject にこのような Error を渡すと、await を囲んだ catch が同じオブジェクトを受け取ります。画面用の文章を返すときは、その message を使えます。
よくある間違い
rejectに文字列を渡す —reject("見つかりません")と書くとerrorは文字列になり、error.messageはundefinedになります。渡すのは必ずErrorのオブジェクトにしてくださいcatchの中でさらに失敗する処理を書く —catchの中で落ちた例外を受け止める場所はありません。catchに書くのは、必ず成功する短い処理だけにします
JavaScript
catch (error) {
return error.response.status; // response が無いと、ここでまた落ちる
}要件
- 準備済みの fetchWork はそのまま使う。作品があれば resolve し、無ければ Error で reject する
- loadWorkOrFallback の try の中で fetchWork(works, id) を await する
- 成功したら
{ ok: true, message: タイトル + " を読み込みました" }を返す - 失敗したら catch で受け止め、
{ ok: false, message: error.message }を返す