非同期のエラーを捕まえる
このレッスンで分かること
- 失敗した 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されたときと同じです。
よくある間違い
rejectに文字列を渡す —reject("見つかりません")と書くとerrorは文字列になり、error.messageはundefinedになります。渡すのは必ずErrorのオブジェクトにしてくださいcatchの中でさらに失敗する処理を書く —catchの中で落ちた例外を受け止める場所はありません。catchに書くのは、必ず成功する短い処理だけにします
JavaScript
catch (error) {
return error.response.status; // response が無いと、ここでまた落ちる
}要件
- fetchWork(works, id) を作る。80 ミリ秒後に、id が一致する作品があれば resolve し、無ければ new Error("作品が見つかりません") で reject する
- loadWorkOrFallback(works, id) を async function として作り、fetchWork の await を try で囲む
- 成功したら
{ ok: true, message: タイトル + " を読み込みました" }を返す - 失敗したら catch で受け止め、
{ ok: false, message: error.message }を返す
入出力例
loadWorkOrFallback([{"id":1,"published":true,"title":"プロフィールサイト","views":120,"year":2026},{"id":2,"published":true,"title":"自己紹介カード","views":48,"year":2026},{"id":3,"published":true,"title":"スキル一覧の表","views":32,"year":2026},{"id":4,"published":true,"title":"作品ギャラリー","views":76,"year":2026},{"id":5,"published":false,"title":"学習ログ","views":0,"year":2025}], 1) → {"message":"プロフィールサイト を読み込みました","ok":true}
loadWorkOrFallback([{"id":1,"published":true,"title":"プロフィールサイト","views":120,"year":2026},{"id":2,"published":true,"title":"自己紹介カード","views":48,"year":2026},{"id":3,"published":true,"title":"スキル一覧の表","views":32,"year":2026},{"id":4,"published":true,"title":"作品ギャラリー","views":76,"year":2026},{"id":5,"published":false,"title":"学習ログ","views":0,"year":2025}], 99) → {"message":"作品が見つかりません","ok":false}
loadWorkOrFallback([], 1) → {"message":"作品が見つかりません","ok":false}
loadWorkOrFallback([{"id":1,"published":true,"title":"プロフィールサイト","views":120,"year":2026},{"id":2,"published":true,"title":"自己紹介カード","views":48,"year":2026},{"id":3,"published":true,"title":"スキル一覧の表","views":32,"year":2026},{"id":4,"published":true,"title":"作品ギャラリー","views":76,"year":2026},{"id":5,"published":false,"title":"学習ログ","views":0,"year":2025}], 5) → {"message":"学習ログ を読み込みました","ok":true}