async / await と try / catch を一言でいうと
async / await と try / catch を組み合わせると、
「非同期処理の成功もエラーも、同期コードと同じように書ける」 ようになります。
awaitで Promise の「成功結果」を普通の値として受け取り- その Promise が
rejectされたときは、その場でthrowされたのと同じように扱われ try / catchでそのエラーをまとめて受け止める
という形です。
ここが重要です。
「Promise の then / catch チェーン」よりも、
「try / catch で一気に囲む」ほうが、
読みやすくて直感的なエラーハンドリングができる、というのが最大のメリットです。
await とエラー(reject)の関係をまず押さえる
Promise が reject されたとき、await では何が起きるか
await は、Promise が resolve(成功)したときは「中身の値」を返しますが、
Promise が reject(失敗)したときは、その場で例外が投げられます。
Promise だけで書くとこうなる処理を:
someAsync()
.then(value => {
console.log("成功:", value);
})
.catch(err => {
console.error("エラー:", err);
});
JavaScriptasync / await で書くと、
中ではこういうことが起きています。
async function run() {
const value = await someAsync(); // ここで失敗すると throw されるイメージ
console.log("成功:", value);
}
JavaScriptこの await someAsync() のところで、reject された場合は 同期コードの throw と同じ扱い になります。
「await は、成功時には値、失敗時には throw」
整理すると:
- 成功(resolve) → await の戻り値として結果が返る
- 失敗(reject) → await の行で例外が投げられる(throw と同じ)
だからこそ、後ろで try / catch が効くようになります。
ここが重要です。
「await = Promise の中身を取り出す」だけでなく、
「await = resolve は戻り値、reject は throw」
という二面性を持っている、と意識してください。
この前提が分かると、try / catch との組み合わせがストンと腑に落ちます。
try / catch と async / await の基本形
一番シンプルなパターン
まずは典型パターンから。
async function run() {
try {
const result = await someAsync();
console.log("成功:", result);
} catch (err) {
console.error("エラー:", err);
}
}
JavaScript流れはこうです。
await someAsync()を実行- 成功 →
resultに値が入り、console.logへ進む - 失敗 → その場で throw →
catchに飛ぶ → エラーログ
この「成功の流れ」と「失敗の流れ」が、
同期コードとほぼ同じ形で表現できます。
複数の await をまとめて try / catch で囲む
async function run() {
try {
const a = await asyncA();
const b = await asyncB(a);
const c = await asyncC(b);
console.log("全部成功:", a, b, c);
} catch (err) {
console.error("どこかでエラー発生:", err);
}
}
JavaScriptここでは、
asyncAで失敗してもasyncBで失敗してもasyncCで失敗しても
全て同じ catch ブロックに飛んできます。
ここが重要です。
Promise の .then().then().catch() でやっていたことを、
「try の中に await を並べて、catch でまとめて受け止める」スタイルに変えることで、
非同期のエラーハンドリングが“普通の処理”と同じ見た目になります。
then / catch との対応関係をしっかり掴む
Promise 版と async / await 版を並べて比較
Promise チェーンで書いた場合:
someAsync()
.then(result => {
return nextAsync(result);
})
.then(finalResult => {
console.log("成功:", finalResult);
})
.catch(err => {
console.error("エラー:", err);
});
JavaScript同じ処理を async / await + try / catch で書くと:
async function run() {
try {
const result = await someAsync();
const finalResult = await nextAsync(result);
console.log("成功:", finalResult);
} catch (err) {
console.error("エラー:", err);
}
}
JavaScriptここで対応関係を整理すると:
- 各
thenの中身 → 各awaitの後ろの行 .catch→catchブロック
という変換になります。
ここが重要です。
「Promise チェーンで書かれているコード」は、
頭の中で「await に置き換えたらどうなるか?」と翻訳してみると、
エラーフローの理解が一気に深まります。catch が try / catch のどこに相当するのかを意識して読む練習をしてみてください。
try / catch を「どこまで」かけるか
全体をざっくり 1 つの try / catch で囲む
一番簡単なのは、「関数の中身ほぼ全部」を try / catch で囲んでしまうことです。
async function run() {
try {
const user = await fetchUser();
const posts = await fetchPosts(user.id);
const comments = await fetchComments(posts[0].id);
console.log("結果:", user, posts, comments);
} catch (err) {
console.error("どこかでエラー:", err);
}
}
JavaScript「細かい場所にこだわらず、とにかく失敗したらここに来てほしい」というときはこの形で十分です。
部分的に try / catch を分ける
一方で、「この await のエラーだけ特別な扱いをしたい」ということもあります。
async function run() {
let user;
try {
user = await fetchUser();
} catch (err) {
console.error("ユーザー取得に失敗:", err);
return; // ここで終わるなど
}
try {
const posts = await fetchPosts(user.id);
console.log("投稿一覧:", posts);
} catch (err) {
console.error("投稿取得に失敗:", err);
}
}
JavaScriptこのように
- ユーザー取得エラー → そこで処理終了
- 投稿取得エラー → エラーを出しつつも、ユーザー情報は利用可能
のように、エラーの種類ごとに try / catch を分割できます。
ここが重要です。
「どこからどこまでを 1 つの try / catch にするか?」は設計のポイントです。
“全部まとめて失敗なら一緒”でいいなら 1 個で囲む。
“ここだけ特別扱いしたい”なら try / catch を分割する。
エラーに対してどう振る舞いたいかから逆算して、範囲を決めていきます。
finally との組み合わせ(後片付け処理)
try / catch / finally の基本
finally ブロックは、
成功しても失敗しても「最後に必ず実行したい処理」を書く場所 です。
async function run() {
console.log("開始");
try {
const result = await someAsync();
console.log("成功:", result);
} catch (err) {
console.error("エラー:", err);
} finally {
console.log("ここは成功/失敗に関わらず必ず実行される");
}
}
JavaScript例えば、
- ローディング表示の ON/OFF
- ファイルや接続のクローズ処理
- 「処理中フラグ」を false に戻す
などを finally に置くケースがよくあります。
ローディングフラグの例
let isLoading = false;
async function run() {
isLoading = true;
try {
const result = await someAsync();
console.log("成功:", result);
} catch (err) {
console.error("エラー:", err);
} finally {
isLoading = false; // 成功でも失敗でも必ず false に戻す
}
}
JavaScriptここが重要です。
非同期処理では、「途中で失敗しても、後片付けはしたい」という場面が多くなります。
try / catch / finally の 3 つセットで、
“成功の流れ” “失敗の流れ” “どちらでもやる後始末” を設計できる、という感覚を身につけてください。
よくある勘違い・落とし穴
try / catch の外側で起きたエラーは捕まえられない
async function run() {
try {
const data = await someAsync();
} catch (err) {
console.error("エラー:", err);
}
// ここで await してエラーになると、この try / catch では捕まえられない
const another = await anotherAsync();
}
JavaScriptanotherAsync() を await している行は、
try ブロックの外側なので、この try / catch では捕まえられません。
解決策はシンプルで、その await を含めて try の中に入れることです。
async function run() {
try {
const data = await someAsync();
const another = await anotherAsync();
} catch (err) {
console.error("どちらかでエラー:", err);
}
}
JavaScriptcatch の中でさらに throw した場合
async function run() {
try {
await someAsync();
} catch (err) {
console.error("一度目の catch:", err);
throw err; // ここでもう一度投げる
}
}
JavaScriptrun() の呼び出し側で、さらに try / catch を置けば、
そこでもう一度エラーを捕まえることができます。
async function main() {
try {
await run();
} catch (err) {
console.error("呼び出し側での catch:", err);
}
}
JavaScriptここが重要です。
try / catch は「その関数の中で閉じた世界」ではありません。
catch の中で throw し直せば、呼び出し側にエラーをバトンタッチできる。
どこで「握りつぶす」のか、どこまで「上に伝える」のかを意識して、
catch の書き方を選ぶ必要があります。
初心者として async / await と try / catch で本当に押さえてほしいこと
await は、「Promise が resolve したら結果の値を返し、reject されたらその場で throw される」。
だから、try / catch で同期コードと同じようにエラー処理ができる。
複数の await を 1 つの try で囲めば、「どれか 1 つでも失敗したら同じ catch に飛ぶ」流れを作れる。
エラーを細かく分けて扱いたいときは、try / catch を分割して書く。
finally を使えば、成功・失敗に関わらず「必ず実行したい後処理」を書ける。
ローディング解除、フラグのリセット、リソースの解放などに便利。
try / catch の外側にある await は、その中では捕まえられない。
エラーを捕まえたい await は必ず try の中に含める。
ここが重要です。
async / await と try / catch を組み合わせると、
「非同期処理だけど、見た目も構造も同期コードそのもの」に近づけられます。
非同期の“時間的なややこしさ”を、コードの見た目からほとんど消してくれるのが、この組み合わせの本当の価値です。
最後に、理解を深めるための練習を 2 つ置いておきます。
// 練習 1:
// 1秒後に成功する Promise または失敗する Promise をランダムに返す関数 randomAsync() を作り、
// async / await + try / catch で結果をログに出してみる。
// 練習 2:
// fetch を使って API から JSON を取得する関数 fetchJson(url) を作り、
// ネットワークエラーや JSON パースエラーを try / catch で捕まえて、
// エラー内容に応じたメッセージを出し分けてみる。
JavaScript実際に「成功したとき」と「わざと失敗させたとき」の挙動を見比べながら、
「どこで await して、どこで catch するか」を自分なりに調整してみると、
async / await と try / catch の感覚がかなり鮮明に掴めてくるはずです。
