JavaScript | 非同期処理:Promise 基礎 – finally の役割

JavaScript JavaScript
スポンサーリンク

まず finally を一言でいうと

finally は、
「Promise が成功しても失敗しても“どっちにしても最後に必ず実行したい処理”を書く場所」です。

例えば、

  • ローディング表示を消したい
  • モーダルを閉じたい
  • ファイルや接続を閉じたい

など、「成功でも失敗でも必ず実行したい片付け処理」を書くのに使います。

ここが重要です。
then は成功時、catch は失敗時、
finally は「成功でも失敗でも、とにかく最後に一回実行」 という役割だとイメージしてください。


同期の try / catch / finally と対応させてイメージする

同期コードの「finally」

まずは、同期の try / catch / finally を思い出します。

try {
  console.log("1: メイン処理");
  throw new Error("エラー発生");
} catch (e) {
  console.log("2: エラー処理");
} finally {
  console.log("3: 後片付け(必ず実行される)");
}
JavaScript

結果はこうなります。

1: メイン処理
2: エラー処理
3: 後片付け(必ず実行される)

ポイントは、エラーがあってもなくても
finally の中身は必ず実行されることです。

これが「必ず最後に実行される場所」という意味の finally です。

Promise の世界の finally

Promise でも、ほぼ同じ役割で finally が使えます。

doSomethingAsync()
  .then((result) => {
    console.log("成功:", result);
  })
  .catch((error) => {
    console.log("失敗:", error);
  })
  .finally(() => {
    console.log("最後に必ず実行される処理");
  });
JavaScript

成功して then が走っても、
失敗して catch が走っても、
そのあとに finally が必ず実行されます。

ここが重要です。
「try / catch / finally」の finally に当たるものが、Promise の .finally
と考えると、とても理解しやすくなります。


finally の基本的な挙動

成功・失敗どちらでも実行される

実際に成功パターンと失敗パターンで挙動を比べてみます。

成功の場合:

function successPromise() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("OK");
    }, 500);
  });
}

successPromise()
  .then((value) => {
    console.log("then:", value);
  })
  .catch((err) => {
    console.log("catch:", err);
  })
  .finally(() => {
    console.log("finally: どっちでも最後に走る");
  });
JavaScript

出力イメージはこうです。

then: OK
finally: どっちでも最後に走る

失敗の場合:

function failPromise() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(new Error("NG"));
    }, 500);
  });
}

failPromise()
  .then((value) => {
    console.log("then:", value);
  })
  .catch((err) => {
    console.log("catch:", err.message);
  })
  .finally(() => {
    console.log("finally: どっちでも最後に走る");
  });
JavaScript

出力イメージはこうです。

catch: NG
finally: どっちでも最後に走る

両方のパターンで finally が必ず実行されているのが分かると思います。

finally のコールバックは「成功・失敗の情報」を受け取らない

thencatch と違い、finally に渡す関数は引数を取りません。

promise.finally(() => {
  // 引数は受け取れない(value や error はここには来ない)
});
JavaScript

初心者がよくやる間違いはこれです。

promise.finally((value) => {
  console.log(value); // これは常に undefined
});
JavaScript

finally の中では「成功した値」や「エラーの中身」は受け取れません。
あくまで、「結果に関係なく実行したい処理」だけを書く場所です。

ここが重要です。
値やエラーを扱いたいのは then / catch、
finally は「後片付け専用」と割り切ると、役割がはっきりします。


finally がよく使われる場面

ローディング表示の ON / OFF

とても典型的な例です。

showLoading(); // ローディング表示 ON

fetch("/api/data")
  .then((res) => res.json())
  .then((data) => {
    console.log("データ取得成功:", data);
  })
  .catch((err) => {
    console.error("データ取得失敗:", err);
  })
  .finally(() => {
    hideLoading(); // 成功でも失敗でもローディングを消す
  });
JavaScript

こうしておけば、

通信が成功したときも
サーバーエラーで失敗したときも
予期せぬエラーが起きたときも

必ず最後に hideLoading() が呼ばれます。

もし finally がないと、

then の中と catch の中で、それぞれ hideLoading() を書いたり、
書き忘れたり、両方に書いて重複したりしがちです。

finally を使うことで、
「どんな結果でも必ずやること」を 1 箇所にまとめられます。

資源の開放・状態のリセット

例えば簡単な例として、「フラグを戻す」ケース。

let isBusy = false;

function doTask() {
  if (isBusy) return;
  isBusy = true;

  return someAsyncTask()
    .then((result) => {
      console.log("結果:", result);
    })
    .catch((err) => {
      console.error("エラー:", err);
    })
    .finally(() => {
      isBusy = false; // 忙しいフラグを必ず戻す
    });
}
JavaScript

ここでは、

処理開始時:isBusytrue にする
どんな結果でも最後:isBusyfalse に戻す

という保証ができます。

ここが重要です。
finally は、「途中で成功・失敗の分岐がいろいろあっても、必ず最後にやるべき後片付けを一箇所に書ける」ことが最大の価値 です。


finally の後ろに then / catch を続けたときの動き

finally も Promise を返す

finally 自体も、thencatch と同じで「新しい Promise」を返します。

基本ルールは次のようなイメージです。

元の Promise が成功していた → finally のあとに続く then は「成功」として動く
元の Promise が失敗していた → finally のあとに続く catch は「失敗」として動く

finally の中で特別なことをしなければ、
元の Promise の「成功 / 失敗」はそのまま次に伝わります。

例:

Promise.resolve("OK")
  .finally(() => {
    console.log("片付け");
  })
  .then((v) => {
    console.log("結果:", v);
  });
JavaScript

出力はこうです。

片付け
結果: OK

元々成功しているので、そのまま成功として次の then に値が渡っています。

finally の中でエラーを投げるとどうなるか

finally の中でエラーが起きた場合、そのエラーが「新しい結果」になります。

Promise.resolve("OK")
  .finally(() => {
    throw new Error("finally でエラー");
  })
  .then((v) => {
    console.log("これは実行されない");
  })
  .catch((err) => {
    console.log("catch:", err.message);
  });
JavaScript

出力イメージ:

catch: finally でエラー

元は成功していましたが、
finally 内でのエラーにより、結果は「失敗」に上書きされ、catch に流れます。

ここが少しだけ重要なポイントです。
通常は finally では「エラーを投げない」(ログや片付けだけを書く)。
もし投げるなら、「それが新しいエラーとして扱われる」ことを意識しておく必要があります。


初心者としての「finally の押さえどころ」

ここまでを、いちばんシンプルにまとめます。

Promise における finally は、「成功でも失敗でも最後に必ず実行したい処理を書く場所」。

同期の try / catch / finally における finally とほぼ同じ役割を持つ。

then は成功時、catch は失敗時、finally はそのどちらでも必ず 1 回だけ実行される。

finally のコールバックは引数を取らない(成功値やエラーはここでは受け取れない)。

典型的な用途は、「ローディング表示の ON/OFF」「フラグや状態のリセット」「リソースの解放(接続を閉じるなど)」。

finally のあとにも then / catch をつなげられ、通常は元の成功・失敗をそのまま次に伝える。
ただし、finally の中でエラーを投げると、そのエラーが新しい失敗として扱われる。

ここが重要です。
finally を「エラーかどうかに関係なく絶対やる後片付けを書く場所」として頭に固定しておくと、
Promise のコードに“安心感”が生まれます。

練習としては、次のような小さなコードを書いてみるといいです。

成功する Promise + finally
失敗する Promise + finally
成功・失敗どちらのときも、finally が必ず実行されるか確認する

それを一度体感すると、
「成功は then」「失敗は catch」「最後の後片付けは finally」という三役の役割分担が、
とてもクリアに感じられるはずです。

タイトルとURLをコピーしました