まず catch を一言でいうと
catch は、
「Promise で起きたエラー(失敗)を、最後にまとめて受け止める場所」
です。
then が「成功したときの続きを書く場所」だとしたら、catch は 「どこかで失敗したときの“落とし穴”として待ち構える場所」 です。
ここが重要です。catch を使うことで、
「非同期処理のどこでエラーが起きても、最後に 1 箇所でエラー処理をまとめて書ける」
ようになります。これはコールバック時代の if (err) { ... } だらけの世界と比べて、とても大きな進歩です。
catch の基本的な使い方
いちばんシンプルな形
形はとても簡単です。
promise.catch(onRejected);
JavaScriptpromise が rejected(失敗)になったとき、
その「エラー情報」を引数として onRejected が呼ばれます。
たとえば:
const p = new Promise((resolve, reject) => {
reject(new Error("何かに失敗しました"));
});
p.catch((error) => {
console.log("エラー:", error.message); // エラー: 何かに失敗しました
});
JavaScriptここで起きていることは、
Promise 作成直後:pending
すぐ reject(...) を呼ぶ → 状態が rejected に変わるcatch に登録されたコールバックが、「失敗理由」を受け取って実行される
という流れです。
then と組み合わせた基本パターン
実務でよく見るのは、こういう形です。
doSomethingAsync()
.then((result) => {
console.log("成功:", result);
})
.catch((error) => {
console.error("失敗:", error);
});
JavaScriptdoSomethingAsync() が返す Promise が、
成功したら → then が呼ばれる
失敗したら → catch が呼ばれる
という、非常に素直な分担になっています。
ここが重要です。
「成功は then」「失敗は catch」という役割分担で考えると、Promise のコードがかなり読みやすくなる
と覚えておいてください。
catch が「どこまでのエラー」を拾うのか
チェーン全体のエラーを一箇所で拾える
Promise の強みは、.then を何段もつなげても、
最後の catch 一つで、途中のどこで発生したエラーもまとめて拾える ことです。
例を見てみます。
doStep1() // Promise を返すとする
.then((r1) => {
console.log("step1:", r1);
return doStep2(r1); // これも Promise
})
.then((r2) => {
console.log("step2:", r2);
return doStep3(r2); // これも Promise
})
.then((r3) => {
console.log("step3:", r3);
})
.catch((error) => {
console.error("どこかで失敗:", error);
});
JavaScriptここでは、
doStep1 が失敗して reject された場合doStep2 が失敗した場合doStep3 が失敗した場合
あるいはそれぞれの then の中で throw された場合
そのどれであっても、
最後の catch に流れてきます。
これは、同期コードでいうと
try {
const r1 = doStep1Sync();
const r2 = doStep2Sync(r1);
const r3 = doStep3Sync(r2);
} catch (e) {
console.error("どこかで失敗:", e);
}
JavaScriptにかなり近いイメージです。
途中の then で throw した場合も catch に届く
次のコードを見てください。
Promise.resolve(10)
.then((v) => {
console.log("v:", v);
throw new Error("ここでエラー");
})
.then((v2) => {
console.log("これは実行されない");
})
.catch((err) => {
console.log("catch で拾った:", err.message);
});
JavaScript流れはこうなります。
Promise.resolve(10) → 10 で成功済みの Promise
1 つ目の then で 10 を受け取り、ログを出したあと throw
その throw が、「内部的に Promise の reject」として扱われる
→ 次の then はスキップされ、catch に飛ぶ
つまり、
reject を明示的に呼ばなくても、then の中で起きた例外は自動的に catch に流れる
という動きになっています。
ここが重要です。
catch は「reject されたエラー」だけでなく、「then の中で投げられた例外」もまとめて受け止める。
これによって、「エラーがどこで起きたか」をいちいち意識せずに、末尾で一括処理できる ようになっています。
then(onFulfilled, onRejected) より catch を使う方が良い理由
then の第2引数にも「エラー処理」は書ける
実は catch を使わなくても、then の第2引数に失敗時の処理を書けます。
promise.then(
(value) => {
console.log("成功:", value);
},
(error) => {
console.log("失敗:", error);
}
);
JavaScriptこれでも動きます。
しかし、これはあまり推奨されません。
中途半端な場所で「エラーを止めてしまいやすい」
問題は、then(onFulfilled, onRejected) を使うと、
「その then の中だけでエラーを止めてしまい、その後ろのチェーンにエラーが伝わらない」
という状況を作りやすいことです。
たとえば:
doStep1()
.then(
(r1) => {
return doStep2(r1);
},
(err) => {
console.log("step1 のエラー:", err);
// ここで何も return しないと、
// 次の then は「成功扱い」で動き始めてしまうことがある
}
)
.then((r2) => {
console.log("step2:", r2);
})
.catch((err) => {
console.error("どこかで失敗:", err);
});
JavaScript途中の onRejected でエラーを「処理したつもり」で終わらせてしまうと、
後続の catch に伝わらず、
「一部だけ挙動が違う」状態になりがちです。
そこで、
成功はすべて then
失敗は最後に catch
という形にしておくと、
「途中でエラーを飲み込んでしまう」ミスが減ります。
実務的に一番シンプルなパターン
なので、基本形としてはこれを覚えておくのをおすすめします。
somePromise
.then(...)
.then(...)
.then(...)
.catch((err) => {
// どこで起きてもここに来るようにする
});
JavaScriptここが重要です。
「エラーはとりあえず最後の catch に全部流す」という設計にしておくと、
Promise チェーン全体の挙動がシンプルで読みやすくなる。
catch も Promise を返す(続きの処理も書ける)
catch のあとにさらに then をつなげられる
catch も then と同じく、「新しい Promise」を返します。
doSomething()
.catch((err) => {
console.error("エラー:", err);
return "デフォルト値"; // エラー時の代わりの値
})
.then((value) => {
console.log("その後の処理:", value);
});
JavaScriptこの例では、
doSomething が成功した場合
→ catch は実行されず、結果が次の then にそのまま渡る
doSomething が失敗した場合
→ catch が呼ばれ、「デフォルト値」を返す
→ 新しい Promise がその「デフォルト値」で成功したものとして扱われる
→ 次の then に value として “デフォルト値” が渡る
という動きになります。
つまり、catch は
エラーをログなどで処理するだけ
+ 必要なら「代わりの値」を返して、処理を続ける
という役割も果たせます。
ここが重要です。
catch も then と同じく、「値(または Promise)を返すことで、エラーから“復帰”して処理を続けることができる」。
必ずしも「そこで終わり」ではない、という点を覚えておくと便利です。
「同期の try/catch」との対応関係をイメージする
同期世界:try/catch
同期コードでは、こう書きます。
try {
const r1 = step1();
const r2 = step2(r1);
const r3 = step3(r2);
console.log("成功:", r3);
} catch (e) {
console.error("どこかでエラー:", e);
}
JavaScriptこの中で例外が起きると、catch に飛んで一括処理できます。
非同期世界:then/catch
Promise では、同じ流れをこう書けます。
step1Async()
.then((r1) => step2Async(r1))
.then((r2) => step3Async(r2))
.then((r3) => {
console.log("成功:", r3);
})
.catch((e) => {
console.error("どこかでエラー:", e);
});
JavaScript見比べると、
try の中 → then のチェーン
catch の中 → Promise の catch
という対応になっているのが分かると思います。
ここが重要です。
Promise の catch は、「非同期版 try/catch の catch」にあたる存在 として考えると、役割がかなりスッキリ理解できます。
初心者向け「catch の押さえどころ」まとめ
ここまでのポイントを、必要最低限に絞ってまとめます。
catch は、Promise が rejected(失敗)になったときに実行する処理を登録するメソッド。
catch は、その前の then チェーンのどこかで起きたエラー(reject や throw)をまとめて受け止める。
then の第2引数でもエラー処理は書けるが、基本は「成功は then」「失敗は最後の catch」という形にするとシンプルで安全。
catch も新しい Promise を返すので、エラー時に代わりの値を返したりして、その後も .then で処理を続けることができる。
同期の try { ... } catch (e) { ... } の catch と、役割的に対応していると考えると理解しやすい。
最後にもう一度だけ強調すると、
ここが重要です。
catch は「エラーを集めて受け止める最後の受け皿」であり、
「エラー処理をバラバラに書いていた世界から、1 箇所にまとめて書ける世界に連れていってくれる道具」 です。
練習としては、
わざと reject する Promise を作る
then を 2〜3 個つないでから catch をつける
どこで失敗させても catch に来るか試す
この 3 ステップをやってみると、
「catch がチェーン全体の“最後の守護神”になっている感覚」が、自分の中でかなりハッキリしてくるはずです。
