Promise のエラーハンドリング(catch・finally)|JavaScript
Promise を使った非同期処理では、通常の try-catch では同期的なエラーしか捕捉できません。Promise 特有のエラーハンドリング方法を理解しておく必要があります。
catch メソッドの基本
Promise がリジェクト(reject)されると、チェーンの中で最初に見つかった catch に処理が移ります。
fetch('/api/users')
.then(response => response.json())
.then(data => {
console.log(data);
})
.catch(error => {
console.error('エラー:', error.message);
});fetch が失敗したり、JSON のパースに失敗したりすると、catch が呼ばれます。
エラーの伝播
then の中で例外がスローされると、その先の catch で捕捉されます。
Promise.resolve('hello')
.then(value => {
throw new Error('then 内でエラー');
})
.then(value => {
console.log('ここは実行されない');
})
.catch(error => {
console.error('キャッチ:', error.message);
});エラーが発生すると、その後の then はスキップされて catch に到達します。
複数の catch
長い Promise チェーンでは、途中で catch を挟むこともできます。
fetchUser()
.then(user => fetchPosts(user.id))
.catch(error => {
console.error('ユーザーまたは投稿の取得に失敗');
return []; // デフォルト値を返して続行
})
.then(posts => renderPosts(posts));catch でエラーを処理した後に値を return すると、次の then に繋がります。
finally メソッド
finally は成功・失敗にかかわらず、最後に必ず実行されます。
showLoadingSpinner();
fetch('/api/data')
.then(response => response.json())
.then(data => displayData(data))
.catch(error => showError(error))
.finally(() => {
hideLoadingSpinner(); // 必ず実行される
});ローディング表示の解除やリソースのクリーンアップに使います。
catch の位置に注意
catch の位置によって、捕捉できるエラーの範囲が変わります。
すべての then で発生したエラーを一括で捕捉する
その時点までのエラーを処理し、後続の処理を続行できる
// パターン1: 最後で一括処理
promise
.then(a => process1(a))
.then(b => process2(b))
.then(c => process3(c))
.catch(handleAllErrors);
// パターン2: 途中で回復
promise
.then(a => process1(a))
.catch(error => defaultValue)
.then(b => process2(b))
.catch(handleError);Promise.all のエラーハンドリング
Promise.all は、どれか1つでもリジェクトされると全体がリジェクトされます。
Promise.all([
fetch('/api/users'),
fetch('/api/posts'),
fetch('/api/comments')
])
.then(responses => {
// すべて成功した場合のみここに来る
})
.catch(error => {
// どれか1つでも失敗するとここに来る
console.error('いずれかのリクエストが失敗:', error);
});すべての結果を取得したい場合は Promise.allSettled を使います。
Promise.allSettled([
fetch('/api/users'),
fetch('/api/posts'),
fetch('/api/comments')
])
.then(results => {
results.forEach((result, i) => {
if (result.status === 'fulfilled') {
console.log(`リクエスト${i}: 成功`);
} else {
console.log(`リクエスト${i}: 失敗`, result.reason);
}
});
});reject と throw の違い
Promise 内でエラーを発生させるには、reject を呼ぶ方法と throw する方法があります。
// reject を使う
new Promise((resolve, reject) => {
reject(new Error('明示的にリジェクト'));
});
// throw を使う
new Promise((resolve, reject) => {
throw new Error('例外をスロー');
});どちらも結果は同じで、catch で捕捉できます。then の中では throw を使います。
未処理のリジェクションを避ける
catch を付け忘れると「Uncaught (in promise)」警告が出ます。必ずエラー処理を入れましょう。
// 悪い例:catch がない
fetchData();
// 良い例:catch を付ける
fetchData().catch(error => {
console.error('エラーを処理:', error);
});未処理のリジェクションはデバッグを困難にし、サイレントな障害を引き起こす可能性があります。