JavaScript | 非同期処理:実務での非同期制御 - 非同期状態管理

JavaScript JavaScript
スポンサーリンク

「非同期状態管理」を一言でいうと

非同期状態管理は、
「今この非同期処理が、どの状態にいるのかをコードと UI で一貫して扱うこと」 です。

例えば、API 呼び出しひとつ取っても、状態はざっくりこう変わります。

  • まだ何もしていない(idle)
  • リクエスト中(loading)
  • 成功してデータがある(success)
  • 失敗してエラーになった(error)

実務では、この状態を
「なんとなくフラグをバラバラに持つ」のではなく、
“1つのまとまった状態”として設計すること がとても大事になります。


なぜ「非同期状態」を意識して管理しないといけないのか

状態を意識しないと、すぐにカオスになる

例えば、よくある「なんとなく書いたコード」はこんな感じです。

let isLoading = false;
let hasError = false;
let data = null;

async function loadUsers() {
  isLoading = true;
  hasError = false;

  try {
    const res = await fetch("/api/users");
    data = await res.json();
  } catch (err) {
    console.error(err);
    hasError = true;
  } finally {
    isLoading = false;
  }
}
JavaScript

一見悪くなさそうですが、
状態が増えてくると、すぐにこうなります。

let isLoading = false;
let hasError = false;
let isEmpty = false;
let isRefreshing = false;
let isInitialLoad = true;
let data = null;
JavaScript

そして画面側では、

if (isLoading && !hasError && isInitialLoad) { ... }
if (!isLoading && hasError) { ... }
if (!isLoading && !hasError && isEmpty) { ... }
JavaScript

みたいな条件が増えていき、
「今この画面は何状態なのか」が自分でも分からなくなります。

ここが重要です。
非同期処理は「時間とともに状態が変わる」ので、
その状態を“バラバラのフラグ”で持つと、すぐに破綻する。
だからこそ「状態をひとまとめに設計する」発想が必要になります。


状態を「1つのオブジェクト」として持つ

状態を列挙して、1つにまとめる

さっきの例を、状態オブジェクトで表現してみます。

const state = {
  status: "idle", // "idle" | "loading" | "success" | "error"
  data: null,
  error: null,
};
JavaScript

状態の変化はこうなります。

async function loadUsers() {
  state.status = "loading";
  state.error = null;

  try {
    const res = await fetch("/api/users");
    const json = await res.json();

    state.status = "success";
    state.data = json;
  } catch (err) {
    state.status = "error";
    state.error = err;
  }
}
JavaScript

画面側は、
status を見て分岐します。

function render() {
  if (state.status === "idle") {
    showMessage("まだ読み込んでいません");
  } else if (state.status === "loading") {
    showLoading();
  } else if (state.status === "error") {
    showError(state.error);
  } else if (state.status === "success") {
    showUsers(state.data);
  }
}
JavaScript

これで、

「今は loading なのか error なのか success なのか」
が一目で分かるようになります。

ここが重要です。
「状態をフラグの組み合わせで表現する」のではなく、
“状態は常にどれか1つ”という形にしておくと、
非同期処理の流れが一気にシンプルになる。


実務でよく出てくる非同期状態のパターン

典型的な状態の流れ

API 1 回分の状態として、よくあるのはこのあたりです。

  • idle(まだ何もしていない)
  • loading(リクエスト中)
  • success(成功してデータあり)
  • error(失敗)

これに加えて、実務では例えばこんな状態も出てきます。

  • refreshing(すでにデータはあるが、裏で再取得中)
  • empty(成功したがデータが空)
  • unauthorized(認証エラー)
  • forbidden(権限なし)

これを全部フラグで持つと地獄なので、
「status を文字列で持つ」 のが定番です。

const state = {
  status: "idle", // "idle" | "loading" | "success" | "error" | "empty" | ...
  data: null,
  error: null,
};
JavaScript

例えば、空データのときはこうします。

if (json.length === 0) {
  state.status = "empty";
  state.data = [];
} else {
  state.status = "success";
  state.data = json;
}
JavaScript

画面側では、

if (state.status === "empty") {
  showMessage("データがありません");
}
JavaScript

と分岐できます。


「状態」と「UI」をきれいに結びつける

状態ごとに UI をはっきり分ける

さっきの render をもう少し丁寧に書いてみます。

function render() {
  clearScreen();

  switch (state.status) {
    case "idle":
      showMessage("まだ読み込んでいません");
      break;
    case "loading":
      showLoading();
      break;
    case "error":
      showError(state.error);
      break;
    case "empty":
      showMessage("データがありません");
      break;
    case "success":
      showUsers(state.data);
      break;
  }
}
JavaScript

ポイントは、

「状態ごとに UI を“排他的”にする」
「同時に loading と error が出る、みたいなことが起きないようにする」

ということです。

ここが重要です。
非同期状態管理のゴールは、
“どの状態のときに、画面がどう見えるか”を
コード上で明確に対応づけること。
状態が曖昧だと UI も曖昧になる。


複数の非同期処理があるときの状態管理

画面全体の状態と、個々のリクエストの状態

実務では、
1画面に複数の非同期処理が同時に存在します。

例:

  • ユーザー情報を取得
  • 通知一覧を取得
  • おすすめ情報を取得

それぞれに状態があります。

const userState = { status: "idle", data: null, error: null };
const notificationsState = { status: "idle", data: null, error: null };
const recommendationsState = { status: "idle", data: null, error: null };
JavaScript

画面全体としては、

「どれか1つでも loading なら、画面上部にローディングバーを出す」
「ユーザー情報だけエラーなら、その部分だけエラー表示」

といった制御をします。

function isAnyLoading() {
  return (
    userState.status === "loading" ||
    notificationsState.status === "loading" ||
    recommendationsState.status === "loading"
  );
}

function renderGlobalLoading() {
  const el = document.getElementById("global-loading");
  el.style.display = isAnyLoading() ? "block" : "none";
}
JavaScript

こうやって、
「個々の非同期状態」と「画面全体の状態」を分けて考える
のが実務的な非同期状態管理です。


「再試行」「キャンセル」「多重送信防止」と状態管理

再試行ボタンと状態

エラー時に「再読み込み」ボタンを出すのもよくあります。

if (state.status === "error") {
  showError(state.error);
  showRetryButton();
}
JavaScript

再試行ボタンを押したら、
また status = "loading" に戻して処理をやり直します。

async function retry() {
  await loadUsers(); // 中で status を loading → success/error に更新
  render();
}
JavaScript

ここで大事なのは、
「再試行も状態遷移の一つ」 として扱うことです。

idle → loading → success
idle → loading → error → loading → success

というふうに、
状態が時間とともに変わっていくイメージを持つと、
設計が安定します。

多重送信防止と状態

フォーム送信のときも、
状態で制御できます。

const submitState = {
  status: "idle", // "idle" | "submitting" | "success" | "error"
  error: null,
};

async function submitForm() {
  if (submitState.status === "submitting") {
    return; // 多重送信防止
  }

  submitState.status = "submitting";
  submitState.error = null;
  renderSubmitButton();

  try {
    await sendForm();
    submitState.status = "success";
  } catch (err) {
    submitState.status = "error";
    submitState.error = err;
  } finally {
    renderSubmitButton();
  }
}
JavaScript

ボタン側はこうなります。

function renderSubmitButton() {
  const button = document.getElementById("submit-button");

  if (submitState.status === "submitting") {
    button.disabled = true;
    button.textContent = "送信中...";
  } else {
    button.disabled = false;
    button.textContent = "送信";
  }
}
JavaScript

ここが重要です。
多重送信防止も、「今は submitting 状態だから新しい送信は受け付けない」という
“状態管理の一部”として考えると、コードが自然になります。


初心者として「非同期状態管理」で本当に押さえてほしいこと

非同期処理には、必ず「状態の変化」がある。
idle → loading → success / error という流れを、
1つの状態オブジェクトとして表現する のが基本。

フラグをバラバラに増やすのではなく、
status を文字列で持ち、
「今はどの状態か」を常に一意にする。

UI は「状態ごとにどう見えるか」をはっきり対応づける。
loading のときはローディング、
error のときはエラー表示、
success のときはデータ表示、
empty のときは「データなし」メッセージ、など。

複数の非同期処理があるときは、
個別の状態と、画面全体の状態(どれか1つでも loading か、など)を分けて考える。

そして何より、
「この処理は今、どの状態にいるのか?」
「その状態のとき、画面はどう見えるべきか?」

を常にセットで考える癖をつけると、
非同期コードが“ただのバラバラな await の集まり”から、
「ちゃんと設計された振る舞い」に変わっていきます。

もし今書いているコードで、
isLoading, hasError, isEmpty みたいなフラグが増え始めていたら、
それは「状態オブジェクトにまとめるタイミング」です。
そこからが、非同期状態管理の本番です。

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