lvncerpedia

JS Promise まとめ

結論から:Promiseは「非同期処理の結果を包むオブジェクト」で、3つの状態を持ち、Reactでは主にuseEffect内のデータ取得で使う。

🔷 Promise の状態遷移

          resolve(値)
pending ──────────────► fulfilled (値が取れた)
  │
  └── reject(エラー)──► rejected  (失敗した)

※ pending から fulfilled/rejected への遷移は一方通行

🔷 基本構文

// 作る側
const p = new Promise((resolve, reject) => {
  if (成功) resolve("データ");
  else      reject(new Error("失敗"));
});

// 使う側(チェーン)
p.then(data => console.log(data))
 .catch(err => console.error(err))
 .finally(() => console.log("必ず実行"));

🔷 async/await(Promiseの糖衣構文)

// これは↑の.then()チェーンと完全に等価
async function fetchData() {
  try {
    const data = await p;   // Promise が resolve するまで待つ
    console.log(data);
  } catch (err) {
    console.error(err);
  }
}

🔷 React での使い方(useEffect + fetch)

import { useState, useEffect } from "react";

function UserCard({ userId }) {
  const [user, setUser]     = useState(null);
  const [error, setError]   = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    // ⚠️ useEffect のコールバック自体は async にできない
    // → 内側に async 関数を定義して即呼び出す
    const fetchUser = async () => {
      try {
        const res  = await fetch(`/api/users/${userId}`);
        if (!res.ok) throw new Error("取得失敗");
        const data = await res.json();
        setUser(data);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };

    fetchUser();
  }, [userId]); // userId が変わるたびに再実行

  if (loading) return <p>Loading...</p>;
  if (error)   return <p>Error: {error}</p>;
  return <p>{user.name}</p>;
}

🔷 よくあるハマりポイント:クリーンアップ

userIdが変わった瞬間に古いリクエストの結果が上書きしてしまう問題(Race Condition)。

useEffect(() => {
  let cancelled = false; // ← フラグで解決

  const fetchUser = async () => {
    const res  = await fetch(`/api/users/${userId}`);
    const data = await res.json();
    if (!cancelled) setUser(data); // アンマウント後は無視
  };

  fetchUser();

  return () => { cancelled = true; }; // クリーンアップ
}, [userId]);

🔷 知っておくべき Promise の並列・競合メソッド

メソッド挙動ユースケース
Promise.all([a, b])全部成功→OK、1つでも失敗→即reject複数APIを並列で全部必要な時
Promise.allSettled([a, b])全部の結果を必ず返す(成否問わず)一部失敗してもまとめて処理したい時
Promise.race([a, b])最初に解決/拒否したものを返すタイムアウト実装
Promise.any([a, b])最初に成功したものを返すフォールバック付きリクエスト
// 並列取得の例(Promise.all)
const [users, posts] = await Promise.all([
  fetch("/api/users").then(r => r.json()),
  fetch("/api/posts").then(r => r.json()),
]);
// 逐次より速い!

🔷 React で本気でやるなら TanStack Query

正直、useEffectでのフェッチ管理はつらくなってくる。
キャッシュ・リトライ・Race Condition 対策・ローディング状態管理を全部やってくれる TanStack Query(旧React Query) が業界標準になってるから、知っておくと絶対損しない。

import { useQuery } from "@tanstack/react-query";

function UserCard({ userId }) {
  const { data, isLoading, error } = useQuery({
    queryKey: ["user", userId],
    queryFn: () => fetch(`/api/users/${userId}`).then(r => r.json()),
  });

  if (isLoading) return <p>Loading...</p>;
  if (error)     return <p>Error!</p>;
  return <p>{data.name}</p>;
}
// さっきの useEffect 版と同じ動作をこれだけで実現できる

まとめ

Promise
  ├── 基本: resolve/reject/then/catch
  ├── async/await: Promiseの書きやすいラッパー
  ├── React: useEffect内でasync関数を即呼び出し
  ├── 並列: Promise.all / allSettled / race / any
  └── 本番: TanStack Query 使うと楽になる