「Promise 内 throw」を一言でいうと
Promise の中で throw する、というのは
「その Promise を reject(失敗状態)にするための、ちょっと短い書き方」 です。
new Promise((resolve, reject) => {
if (何かがおかしい) {
throw new Error("エラーです");
}
resolve("成功");
});
JavaScriptこれは実は、
new Promise((resolve, reject) => {
if (何かがおかしい) {
reject(new Error("エラーです"));
return;
}
resolve("成功");
});
JavaScriptとほぼ同じ意味になります。
ここが重要です。
Promise の中で throw すると、そのエラーは「外側の try / catch」には飛ばず、
「その Promise の reject」として扱われ、.catch(...) や await の try / catch に流れていきます。
この「どこに飛ぶのか」をちゃんとイメージできるかどうかが、Promise 内 throw を理解するカギです。
Promise 内での throw はどう扱われるのか
コンストラクタの中での throw
まずは一番基本の形から。
const p = new Promise((resolve, reject) => {
console.log("Promise 開始");
if (true) {
throw new Error("ここでエラー");
}
resolve("成功");
});
p.then((value) => {
console.log("then:", value);
}).catch((err) => {
console.log("catch:", err.message);
});
JavaScriptこのコードの流れを追ってみます。
new Promise(...)が呼ばれる- すぐに中の関数(executor と呼ばれる)が実行される
throw new Error("ここでエラー")が実行される- JavaScript は「Promise の中で throw された」と判断し、その Promise を
reject状態にする - その結果、
thenはスキップされ、catchが呼ばれる
実際の出力はこうなります。
Promise 開始
catch: ここでエラー
ここが重要です。
Promise のコンストラクタの中で throw すると、
「その Promise は reject された」とみなされ、
外側の .catch(...) に流れていきます。
外側の同期的な try / catch には届きません。
外側の try / catch では捕まらない例
次のコードを見てください。
try {
new Promise((resolve, reject) => {
throw new Error("Promise 内でエラー");
});
} catch (err) {
console.log("捕まえた?:", err.message);
}
JavaScript直感的には「try で囲んでるから捕まるのでは?」と思いがちですが、
この catch は呼ばれません。
なぜかというと、
new Promise(...) の中で起きた throw は、
「その Promise を reject する」という形に変換されるだけで、
同期的な例外として外に飛び出さないからです。
このエラーを扱いたいなら、Promise 側で .catch(...) する必要があります。
new Promise((resolve, reject) => {
throw new Error("Promise 内でエラー");
})
.catch((err) => {
console.log("Promise の catch で捕まえた:", err.message);
});
JavaScriptここが重要です。
「Promise 内 throw」は、「Promise の reject になる」のであって、
「外側の try / catch に飛ぶ同期例外」ではありません。
この違いを混同すると、“try / catch 書いたのに捕まらない問題” にハマります。
then の中で throw した場合
then の中の throw も「次の catch に流れる」
Promise コンストラクタの中だけでなく、then のコールバックの中で throw しても、同じように扱われます。
fetch("https://example.com/api/data")
.then((response) => {
if (!response.ok) {
throw new Error("HTTP エラー: " + response.status);
}
return response.json();
})
.then((data) => {
console.log("成功:", data);
})
.catch((err) => {
console.log("catch で捕まえた:", err.message);
});
JavaScriptここで起きうるエラーは、例えば次のようなものです。
HTTP ステータスが 200 以外で、自分で throw new Error(...) したresponse.json() が失敗して throw した
ネットワークエラーで fetch 自体が reject した
これらはすべて、最後の .catch(...) に流れてきます。
なぜかというと、
then の中で throw すると、
「その then が返す Promise が reject される」
というルールになっているからです。
ここが重要です。
Promise チェーンの中では、
「throw は reject と同じように、“下流の catch に流すためのシグナル”」として使えます。throw を使うと、return Promise.reject(...) よりも短く、読みやすく書けることが多いです。
async / await から見た「Promise 内 throw」
await は「reject を throw に変換する」
async / await を使うと、
Promise の reject は「同期例外のように」扱われます。
function getData() {
return new Promise((resolve, reject) => {
throw new Error("Promise 内でエラー");
});
}
async function main() {
try {
const data = await getData();
console.log("成功:", data);
} catch (err) {
console.log("async/await の catch:", err.message);
}
}
main();
JavaScriptここで起きていることを整理します。
getData() の中で throw
→ Promise が reject 状態になる
await getData()
→ reject された Promise を await すると、その場で throw されたかのように扱われる
その結果、main 関数の try / catch の catch に入ります。
つまり、
Promise 内 throw
→ Promise の reject
await
→ reject を同期例外に変換
try / catch
→ その同期例外を捕まえる
という三段階の変換が起きています。
ここが重要です。
async / await の世界では、
「Promise 内 throw → reject → await で throw → try / catch で捕まえる」
という流れになっている、とイメージしてください。
これが分かると、「Promise 内 throw」と「async / await の try / catch」が頭の中で一本につながります。
reject と throw をどう使い分けるか
reject を直接呼ぶパターン
もちろん、Promise の中で reject を直接呼んでも構いません。
function getUser(id) {
return new Promise((resolve, reject) => {
if (!id) {
reject(new Error("ID が指定されていません"));
return;
}
setTimeout(() => {
resolve({ id, name: "太郎" });
}, 1000);
});
}
JavaScriptこの場合、reject を呼んだ時点で Promise は reject 状態になります。
throw を使わずに reject を使うメリットは、
「非同期の中でも明示的に reject できる」ことです。
例えば、setTimeout の中でエラーにしたいときは、throw ではなく reject を使う必要があります。
new Promise((resolve, reject) => {
setTimeout(() => {
// ここで throw しても、Promise の外に飛んでしまう
// throw new Error("ダメな例");
// 正しくは reject を呼ぶ
reject(new Error("タイマー後にエラー"));
}, 1000);
});
JavaScriptthrow を使うのに向いている場所
逆に、throw が向いているのは、
「Promise の executor や then の中で、同期的にエラーを表現したいとき」です。
new Promise((resolve, reject) => {
if (!url) {
throw new Error("URL が必要です");
}
resolve(url);
});
JavaScriptfetch("/api/data")
.then((response) => {
if (!response.ok) {
throw new Error("HTTP エラー: " + response.status);
}
return response.json();
})
.catch((err) => {
console.error("エラー:", err.message);
});
JavaScriptここが重要です。
Promise の中で「同期的に」エラーを出したいなら throw、
「非同期のコールバックの中で」エラーを出したいなら reject、
という使い分けを意識すると、挙動が直感に合いやすくなります。
「Promise 内 throw」で初心者がハマりやすいポイント
外側の try / catch で捕まえようとしてしまう
よくあるパターンがこれです。
try {
new Promise((resolve, reject) => {
throw new Error("エラー");
});
} catch (err) {
console.log("捕まえたつもり:", err.message);
}
JavaScript「try で囲んでるのに捕まらない…」となります。
これはもう一度はっきりさせておきたいのですが、
Promise 内 throw
→ Promise の reject
→ 外側の同期的な try / catch には飛ばない
というルールです。
扱いたいなら、Promise 側で .catch(...) するか、
async / await で await してから try / catch する必要があります。
catch を付け忘れて「未処理の Promise 拒否」になる
Promise 内 throw を使っているのに、
最後に .catch(...) を付け忘れると、
ブラウザコンソールに「Unhandled Promise Rejection」的な警告が出ます。
new Promise((resolve, reject) => {
throw new Error("エラー");
});
// どこにも catch がない
JavaScriptこれは、「reject された Promise が、どこにも処理されていない」という状態です。
ここが重要です。
Promise 内 throw を使うときは、
「この Promise は最終的にどこで catch されるのか?」を必ず意識してください。
catch のない Promise 内 throw は、“投げっぱなしのエラー” になります。
初心者として「Promise 内 throw」で本当に押さえてほしいこと
Promise の中で throw すると、その Promise は reject 状態になる。
それは「外側の同期的な try / catch」には届かず、
Promise の .catch(...) や、await を含む try / catch に流れていく。
Promise コンストラクタや then の中での throw は、return Promise.reject(new Error(...)) とほぼ同じ意味。
ただし、setTimeout など「さらに非同期の中」では throw ではなく reject を使う必要がある。
async / await の世界では、
「Promise 内 throw → reject → await で throw → try / catch で捕まえる」
という流れになっている。
そして一番大事なのは、
「この throw は、最終的にどこで捕まえるつもりなのか?」を常に意識すること。
Promise 内 throw を書いたら、
「この Promise の .catch(...) はどこ?」
「それとも、どこかで await して try / catch している?」
と自分に問いかけてみてください。
その問いを習慣にすると、
「エラーがどこかに消える」「コンソールだけ赤くなる」といった不安定なコードから、
「エラーの流れがちゃんと見える」コードに、一歩ずつ近づいていきます。
