JavaScript | 非同期処理:Promise 応用 – 条件付き非同期処理

JavaScript JavaScript
スポンサーリンク

「条件付き非同期処理」って何かをまずイメージする

Promise を使っていると、だんだんこういう場面が出てきます。

「A の結果しだいで、B を呼ぶか呼ばないか決めたい」
「ログイン済みなら API を叩く、未ログインならローカルのデータを使いたい」
「エラーの内容によっては、リトライ(再実行)したい」

つまり、「条件(if)と非同期処理(Promise)を組み合わせて制御したい」 という状況です。

これがここでいう「条件付き非同期処理」です。

ここが重要です。
Promise の世界では、
「条件分岐そのものはふつうの if」+「分岐ごとに Promise を返す」
というルールさえ押さえれば、ほとんどの条件付き非同期処理をきれいに書けます。


基本パターン1:前の結果を見て、次の非同期処理をするか決める

具体例のイメージ

例えばこんな要件を考えます。

「ユーザー情報を取得して、そのユーザーが管理者(admin)なら管理者用 API を叩く。
そうじゃないなら、一般ユーザー用 API を叩く。」

疑似コードで書くと、

  1. fetchUser()user を取得する(Promise)
  2. user.isAdmintrue なら fetchAdminData() を叩く
  3. そうでなければ fetchNormalData() を叩く
  4. どちらにせよ、最後に「取得したデータ」を次の then に渡したい

これを Promise で書いていきます。

then の中で条件分岐して、Promise を return する

まずは関数のイメージから。

function fetchUser() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({ id: 1, name: "Taro", isAdmin: false });
    }, 500);
  });
}

function fetchAdminData() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("管理者用データ");
    }, 500);
  });
}

function fetchNormalData() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("一般ユーザー用データ");
    }, 500);
  });
}
JavaScript

これを「条件付き非同期処理」でつなげます。

fetchUser()
  .then((user) => {
    console.log("ユーザー:", user);

    if (user.isAdmin) {
      return fetchAdminData();   // 管理者ならこっちの Promise を返す
    } else {
      return fetchNormalData();  // それ以外ならこっち
    }
  })
  .then((data) => {
    console.log("最終的なデータ:", data);
  })
  .catch((err) => {
    console.error("どこかで失敗:", err);
  });
JavaScript

ここで起きていることを丁寧に言葉で追うと、

  1. fetchUser() がユーザー情報を返す
  2. then の中で user.isAdmin をチェックする
  3. 条件に応じて「どちらか一方の Promise」を return する
  4. 次の then は、「選ばれた側の Promise の結果」を data として受け取る

つまり、条件分岐の結果として「返す Promise が変わる」 というだけです。

ここが重要です。
「非同期の条件分岐をしたいときは、then の中で if を使って、“どの Promise を return するか” を条件で切り替える。
次の then は、どちらの分岐であっても「戻ってきた値」を同じように受け取れる。


基本パターン2:「条件次第で何もしない」場合の書き方

「条件を満たさないならスキップしたい」ケース

例えば、

「既にキャッシュがあるなら API を叩かずにそのまま返す。
キャッシュが無ければ API を叩いて結果を返す。」

のようなケースです。

疑似コードで言うと、

  1. getFromCache() でキャッシュを探す(同期でも非同期でもよい)
  2. 見つかったらそのまま値を返す
  3. 見つからなかったら fetchFromServer() を呼ぶ

これを Promise で書くときも、考え方は同じです。

すでにある値を Promise.resolve で包む

キャッシュは同期的に取れるとしましょう(単純な例として配列の検索など)。

const cache = {
  user: { id: 1, name: "Taro" },
};
JavaScript

API はこんな感じにしておきます。

function fetchUserFromServer() {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log("サーバーから取得");
      resolve({ id: 1, name: "Taro (from server)" });
    }, 1000);
  });
}
JavaScript

キャッシュを優先する条件付き非同期処理はこう書けます。

function getUserWithCache() {
  if (cache.user) {
    console.log("キャッシュから取得");
    return Promise.resolve(cache.user);  // すでにある値を Promise として返す
  } else {
    return fetchUserFromServer();        // 非同期 API の Promise を返す
  }
}

getUserWithCache()
  .then((user) => {
    console.log("結果:", user);
  })
  .catch((err) => {
    console.error("エラー:", err);
  });
JavaScript

ここでのポイントは、

「キャッシュがある」場合も「API を叩く」場合も、
どちらも最終的に「Promise を返す」 ようにしていることです。

キャッシュあり → Promise.resolve(既存の値)
キャッシュなし → fetchUserFromServer()(Promise)

呼び出し側から見ると、どちらのパターンでも「Promise を返す関数」として扱える ので、
いつもどおり then / catch で処理できます。

ここが重要です。
条件で「非同期するかしないか」を切り替えたいときは、「どのパスでも Promise を返す」ように揃える。
同期の値は Promise.resolve で非同期インターフェースに合わせる。


パターン3:条件次第で「さらに別の非同期処理を足す」

例:ログイン済みなら追加入力、そうでないならスキップ

例えばこんな流れです。

  1. ユーザー情報を取得
  2. ユーザーが「プロフィール未設定」なら、プロフィール情報を別 API から取得
  3. すでに「プロフィール済み」なら、そのステップはスキップ

これも「条件付き非同期処理」です。

実装イメージ

仮の関数を作ります。

function fetchUser() {
  return new Promise((resolve) => {
    setTimeout(() => {
      // profileCompleted が false だとする
      resolve({ id: 1, name: "Taro", profileCompleted: false });
    }, 500);
  });
}

function fetchProfile(userId) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({ age: 20, country: "Japan" });
    }, 500);
  });
}
JavaScript

これを条件付きでつなげます。

fetchUser()
  .then((user) => {
    if (!user.profileCompleted) {
      // プロフィール未設定なら、プロフィール取得 API を呼ぶ
      return fetchProfile(user.id).then((profile) => {
        // user に profile を合成して次へ渡す
        return { ...user, profile };
      });
    } else {
      // すでにプロフィール完了なら、そのままの user を次へ
      return user;
    }
  })
  .then((userWithProfile) => {
    console.log("最終ユーザー情報:", userWithProfile);
  })
  .catch((err) => {
    console.error("エラー:", err);
  });
JavaScript

ここで大事なのは、if の中でもさらに Promise チェーンを作っているところです。

return fetchProfile(...).then((profile) => { ...; return マージした値; });

この一連も、結局は 「1つの Promise を返している」 と同じになります。

まとめると、

  • 必要に応じて追加の非同期処理を挟みたいとき
    → then の中で if し、その中でさらに Promise チェーンを組み立てて return する
  • 不要なとき
    → 同じ型(オブジェクトなど)をそのまま return する

という構造です。

ここが重要です。
条件付きで「非同期処理をもう一段増やす」場合でも、最終的に「then の return で Promise か値を返す」という形にしておけば、次の then からは常に同じ形で扱える。


パターン4:条件によって「エラーにする / 続行する」を切り替える

条件によって失敗扱いにしたい

例えば、

レスポンスが 200 なら成功
レスポンスが 400 以上ならエラー(reject)にしたい

というケースです。

fetch などは、通信自体は成功していてもステータスコードが 404 だったりするので、
自分で「成功 / 失敗」を判定する必要があります。

then の中で条件チェックして reject する

簡単な例を考えます。

function fakeApi(status) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({ status, data: "レスポンスボディ" });
    }, 500);
  });
}
JavaScript

これを条件付きでエラーにします。

fakeApi(404)
  .then((res) => {
    if (res.status >= 400) {
      // ここでエラー扱いにしたい
      throw new Error(`HTTP エラー: ${res.status}`);
      // または: return Promise.reject(new Error(...));
    }

    return res.data;  // 正常時は data を次へ渡す
  })
  .then((data) => {
    console.log("成功データ:", data);
  })
  .catch((err) => {
    console.error("catch で拾ったエラー:", err.message);
  });
JavaScript

ポイントは、

then の中で条件を見て、
NG なら throwPromise.reject で「意図的にエラーにする」
OK なら普通に値を返す

というやり方です。

ここが重要です。
「条件によって失敗扱いにしたい」場合は、then の中で if し、NG のときだけ throw(または Promise.reject)。
これで、その先の then はスキップされて catch に流れる。


async/await の場合との対比(考え方は同じ)

async/await に直すとどう見えるか

さきほどの「管理者なら管理者 API、一般なら一般 API」を、async/await で書くとこうなります。

async function main() {
  try {
    const user = await fetchUser();
    let data;

    if (user.isAdmin) {
      data = await fetchAdminData();
    } else {
      data = await fetchNormalData();
    }

    console.log("最終データ:", data);
  } catch (err) {
    console.error("どこかで失敗:", err);
  }
}

main();
JavaScript

Promise 版では「then の中で if + Promise を return」でしたが、
async/await 版では「普通の if と await」がほぼそのまま書けています。

でも、やっていることは同じです。

  • if で条件分岐
  • 分岐先で「await する Promise」が変わるだけ
  • どちらの分岐でも、最終的に data を得てから次へ進む

ここが重要です。
async/await を使っても、「条件付き非同期処理」の考え方は同じ。
if で分岐して、分岐先で待つ Promise が違うだけ。
Promise 版ではそれを「then の中で return する」という形で表現している。


初心者として押さえておくべき「条件付き非同期処理」のコツ

最後に、条件付き非同期処理を Promise で書くときのコツをシンプルにまとめます。

条件そのものは普通の if / else で書いてよい。
「Promise だから特別な if が要る」わけではない。

「どの分岐でも Promise を返す」ように揃えると、呼び出し側から見て扱いやすくなる。
同期値は Promise.resolve(value) で包んでしまうと、インターフェースが統一される。

then の中で条件分岐するときは、「分岐ごとに return する Promise や値を決める」。
次の then は、その「return されたもの」をそのまま受け取るだけ。

条件で失敗扱いにしたいときは、then の中で if して、NG 条件で throw する。
それによって、その先の then はスキップされ、catch に流れる。

ここが重要です。
条件付き非同期処理は、「普通の if の世界」と「Promise チェーンの世界」をつなぐ部分。
やっていることは、「条件ごとにどの Promise(または値)を返すかを選び、そのバトンを次の then に渡している」だけです。

おすすめの練習としては、

「簡単なフラグ(isAdmin や useCache など)を使って、
条件によって呼ぶ Promise を変える関数」を
自分で 2〜3 個書いてみることです。

それを then チェーンで繋いで、

条件を変えたときにどの API が呼ばれ、何が最終結果として then に届くのか

を console.log で確認していくと、
条件付き非同期処理の感覚がかなりしっかり身についていきます。

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