async / await と finally を一言でいうと
async / await と finally を組み合わせると、
「非同期処理が成功しても失敗しても、必ず最後にやりたい処理」を書ける ようになります。
try / catch / finally の流れはこうです。
try… 普通にやりたい処理(awaitを含む)catch… エラーになったときだけ実行したい処理finally… 成功でも失敗でも、最後に必ず実行したい処理
ここが重要です。
非同期処理では「あ、エラーで止まったけど、ローディング表示が消えてない」「フラグが true のまま」などのミスが起きがちです。finally は、その「後片付け」「後始末」を確実に行うための場所 だと覚えてください。
try / catch / finally の基本の形(async でも同じ)
まずは同期コードの例でイメージを掴む
非同期の前に、普通の(同期)コードで考えます。
try {
console.log("try: メインの処理");
throw new Error("エラー発生");
} catch (err) {
console.log("catch:", err.message);
} finally {
console.log("finally: 成功でも失敗でも必ず実行");
}
JavaScript実行順はこうなります。
tryが実行される- 途中で
throw→catchに飛ぶ catchのあと、finallyが必ず実行される
try 内でエラーが起きても、finally は必ず実行されます。
async / await でも流れはまったく同じ
async 関数の中で await を使うときも、
構造は同じです。
async function run() {
try {
console.log("try: 非同期処理の前");
const result = await someAsync(); // 成功 or 失敗
console.log("try: 非同期処理の後", result);
} catch (err) {
console.log("catch:", err.message);
} finally {
console.log("finally: 成功でも失敗でもここは実行される");
}
}
JavaScriptsomeAsync() が
- 成功(resolve) →
catchは飛ばさず、tryの後ろ →finally - 失敗(reject) →
catchに飛んで、その後finally
という動きになります。
ここが重要です。
「非同期だろうが同期だろうが、try / catch / finally のルールは同じ」。
違うのは、“エラーが起きるタイミングが「await の行」になる” だけです。
典型パターン①:ローディング表示と finally
よくある「ローディング中」フラグの問題
非同期処理では、よく「読み込み中」のフラグやローディング表示を使います。
let isLoading = false;
async function fetchData() {
isLoading = true;
const data = await fetchSomething(); // ここでエラーになったら?
isLoading = false;
}
JavaScriptもし fetchSomething() がエラーになって await のところで例外が投げられると、isLoading = false; に到達せず、「ずっとローディング中」のままになります。
これは UI では致命的です。
finally で必ずフラグを戻す
finally を使うと、こう書けます。
let isLoading = false;
async function fetchData() {
isLoading = true;
try {
const data = await fetchSomething();
console.log("データ取得成功:", data);
} catch (err) {
console.error("データ取得失敗:", err);
} finally {
isLoading = false; // 成功でも失敗でも必ず false に戻る
}
}
JavaScript動きはこうです。
- 成功 → try の中が最後まで実行 → finally
- 失敗 → try の途中で catch に飛ぶ → finally
どちらのルートでも、最後に isLoading = false; が必ず実行されます。
ここが重要です。
「状態を元に戻す処理」「UI をリセットする処理」は、
成功 / 失敗どちらでもやりたいことが多い。
それを安全に書く場所が finally です。
典型パターン②:リソースの解放・後片付け
「開く → 使う → 閉じる」が必要なもの
例えば、擬似的に「接続を確立 → データ取得 → 接続を閉じる」という処理を考えてみます。
async function process() {
const connection = await openConnection(); // 接続を開く
const data = await connection.getData(); // ここでエラーになるかも?
await connection.close(); // エラーになるとここまで来ない
}
JavaScriptこのままだと、getData() でエラーが起きた場合、connection.close() が呼ばれず、接続が開きっぱなしになります。
finally に「閉じる処理」を書く
async function process() {
const connection = await openConnection();
try {
const data = await connection.getData();
console.log("取得データ:", data);
} catch (err) {
console.error("データ取得に失敗:", err);
} finally {
await connection.close(); // 成功/失敗に関係なく必ず閉じる
}
}
JavaScriptここが重要です。
「開いたものを閉じる」「開始したものを終了する」
といった対になる操作は、
「開く」→ try、「閉じる」→ finally に配置するのが定石です。
これにより、予期せぬエラーが発生しても、システムが“開きっぱなし”になりません。
典型パターン③:フラグや状態のリセット
「処理中フラグ」を扱う例
少し現実的な UI をイメージしてみます。
let isProcessing = false;
async function onClickButton() {
if (isProcessing) return; // 二重クリック防止
isProcessing = true;
try {
await doSomethingAsync();
console.log("処理成功");
} catch (err) {
console.error("処理失敗:", err);
} finally {
isProcessing = false; // 必ず解除
}
}
JavaScriptこのパターンでは、
- クリックされたらまず
isProcessingを確認 - すでに true なら何もしない(二重起動防止)
- 処理が終わったら(成功・失敗問わず)finally で false に戻す
という「よくある UI の書き方」が、
とても素直に実現できます。
finally を使わないと起きる事故
try / catch だけで同じことを書くと、await のあとに isProcessing = false を置き忘れたり、
エラー処理の中にだけ書いて成功時に戻し忘れたり、
かなりミスりやすくなります。
ここが重要です。
「どの出口から出ても、最後に必ず通ってほしい場所」があるなら、
そこに書くべきコードは必ず finally に置きましょう。
フラグ、ローディング、リソース解放…全部ここです。
finally 内で await を使うときの挙動
finally の中でも await は普通に使える
async 関数の中であれば、finally の中でも await を使えます。
async function run() {
let resource = null;
try {
resource = await openResource();
await useResource(resource);
} catch (err) {
console.error("エラー:", err);
} finally {
if (resource) {
await resource.close(); // ここでも非同期の後片付けができる
}
}
}
JavaScriptこのとき、finally の中の await が終わるまで、run() 全体は「まだ完了していない」状態です。
つまり、
run()の Promise は、finally 内の処理が終わるまで resolve されない- 呼び出し側が
await run()しているなら、「後片付けが終わるまで」待つ
という動きになります。
finally でエラーが起きた場合
もし finally の中でエラーが起きると、
元の try / catch の結果とは別に、
そのエラーが「最終的な失敗」として外に伝わります。
async function run() {
try {
await doMainTask(); // ここで失敗するかも
} catch (err) {
console.error("メイン処理のエラー:", err);
} finally {
console.log("後片付け開始");
throw new Error("後片付け中に失敗"); // これが最終的なエラーになる
}
}
JavaScript呼び出し側が await run() していれば、
この「後片付け中に失敗」が catch に飛んできます。
ここが重要です。
finally の中も「普通のコード」と同じで、await も使えるしエラーも起きる。
「後片付けのほうが大事な世界」では、
finally 内のエラーを呼び出し側に伝える設計も必要になります。
finally を「使うべき場面」を見抜くコツ
こんなときは finally を検討する
コードを書いていて、こんなフレーズが頭に浮かんだら、
それは finally の出番です。
「エラーでもこれだけは絶対やりたい」
「成功でも失敗でも、最後にこれで締めたい」
「どこで失敗しても、リソースは必ず閉じておきたい」
「ローディング表示は絶対消し忘れたくない」
これらはすべて finally 向きです。
逆に「catch の中だけ」でやるべきこと
一方で、
「失敗したときだけログを出したい」「失敗したときだけリトライしたい」
といった処理は、catch に書くのが自然です。
整理すると:
- 成功時だけ …
tryの成功パスに書く - 失敗時だけ …
catchに書く - 成功・失敗どちらでも …
finallyに書く
ここが重要です。
try / catch / finally を「3 つの出口」として考えてください。
「成功の出口」「失敗の出口」「どちらでも必ず通る出口」。
finally はその 3 つ目の出口であり、“必ず通る” という保証こそが価値です。
初心者として async / await + finally で本当に押さえてほしいこと
async / await と try / catch / finally の基本構造は、同期コードのそれと同じ。
違うのは、await の行で Promise の成功・失敗が発生する点だけ。
finally は、「成功しても失敗しても必ず実行したい処理」を書く場所。
ローディングの解除、フラグのリセット、接続のクローズなど、「後片付け」を置くのが定石。
finally の中でも await は使える。
その場合、finally 内の処理が終わるまで、その async 関数は resolve されない。
finally の中でエラーが起きた場合、それが最終的なエラーとして外に伝わる。
「後片付けの失敗」をどう扱うかも設計の一部。
ここが重要です。
非同期処理では、「途中で失敗しても、状態だけは必ず元に戻したい」という場面がとても多いです。
async / await と finally をセットで使えるようになると、
そうした“後始末込みの設計”を、落ち着いて・安全に書けるようになります。
最後に、小さな練習を 2 つ置いておきます。
// 練習 1:
// 1秒後に成功または失敗する Promise をランダムに返す関数 randomAsync() を作り、
// それを await する async 関数の中で、
// - try: 結果を表示
// - catch: エラーを表示
// - finally: "終了処理" とログを出す
// を実装して、毎回 finally が必ず実行されることを確認する。
// 練習 2:
// isLoading フラグを使って、「処理中は true、終わったら必ず false」にするコードを
// finally あり / なし の両方で書いてみて、
// エラー時にどう挙動が変わるかを体感してみる。
JavaScript自分で「わざと失敗させてみる」→「finally がどう動くかを見る」という体験をすると、
finally がただの構文ではなく、“安心して非同期を書けるための土台” だと実感できるはずです。
