C
JavaScript/非同期/Lesson 16

Promise — *非同期の標準*

1時間·theory
このチャプター
2/3

Promise — *非同期の標準*

🎯 このlessonを読み終えたら

このlessonを読み終えると、以下の3つを自信を持って行えるようになります。

  • ✅ Pending → Fulfilled / Rejected の状態遷移
  • ✅ .then / .catch / .finally のチェーン
  • ✅ Promise.all · race · any · allSettled の違い

学習目標をチェックリストとして手元に置き、すべて答えられるようになったらlessonを閉じてください。

Promiseとは何か

核心を一言で

Promise = 「今は結果がないが、後で渡すと約束した」オブジェクト。非同期処理の標準。コールバック地獄を解決した2015年ES6の大きな進歩

コールバック地獄 — 旧来の方式

javascript
// 🧪 偽API — コールバック スタイル (Node.js 旧標準)
const fetchUser   = (id,   cb) => setTimeout(() => cb(null, { id, name: 'ホン' }), 50);
const fetchOrders = (uid,  cb) => setTimeout(() => cb(null, [{ id: 1 }, { id: 2 }]), 50);
const fetchItems  = (oid,  cb) => setTimeout(() => cb(null, [{ name: '本' }]), 50);
const handle = (err) => console.error('失敗:', err);
const id = 42;
javascript
fetchUser(id, (err, user) => {
    if (err) return handle(err);
    fetchOrders(user.id, (err, orders) => {
        if (err) return handle(err);
        fetchItems(orders[0].id, (err, items) => {
            if (err) return handle(err);
            console.log(items);   // [{ name: '本' }]
            // インデントが*右に*無限増加 → コールバック地獄
        });
    });
});

コールバックの中にコールバックの中にコールバック...ピラミッドのようにインデントが増殖。コールバック地獄

Promiseの方式

javascript
// 🧪 偽API — Promise スタイル (モダン標準)
javascript

const wait = (ms) => new Promise(r => setTimeout(r, ms));

const fetchUser = (id) => wait(50).then(() => ({ id, name: 'ホン' }));

const fetchOrders = (uid) => wait(50).then(() => [{ id: 1 }, { id: 2 }]);

const fetchItems = (oid) => wait(50).then(() => [{ name: '本' }]);

const handle = (err) => console.error('失敗:', err);

const id = 42;

fetchUser(id)

.then(user => fetchOrders(user.id))

.then(orders => fetchItems(orders[0].id))

code
    .then(items  => console.log(items))   // 📤 [{ name: '本' }]
    .catch(err   => handle(err));

// 💡 コールバック地獄のインデント → 水平的チェイニング。エラーも最後で1回catch。

チェーンで水平的に。エラーも末尾の一度のcatchだけで処理。

Promiseの3つの状態

javascript
const success = true;
const value = 42;

const p = new Promise((resolve, reject) => {
    // 1️⃣ pending (待機中) — 最初生成された瞬間
    if (success) {
        resolve(value);   // 2️⃣-A fulfilled (成功) → then で受け取る
    } else {
        reject(new Error('失敗!'));  // 2️⃣-B rejected (失敗) → catch で受け取る
    }
});

p.then(v   => console.log('成功:', v))     // 成功時に実行
 .catch(err => console.error('失敗:', err)) // 失敗時に実行
 .finally(() => console.log('終了!'));         // 成功/失敗に関わらず常に実行

// 📤 出力:
//   成功: 42
//   終了!

一度fulfilledまたはrejectedになると変更不可

よく使われるPromise

javascript
// fetch — 標準 HTTP
fetch('/api/users/42')
    .then(res => res.json())
    .then(data => console.log(data));

// setTimeout のPromiseバージョン
javascript

const wait = (ms) => new Promise(r => setTimeout(r, ms));

wait(1000).then(() => console.log("1秒後"));

// Promise.resolve — すでに完了したPromise

const p = Promise.resolve(42);

p.then(v => console.log(v)); // 42

code

## 並列処理 — Promise.all

javascript

// 🧪 偽API (コピペ可能に)

const wait = (ms) => new Promise(r => setTimeout(r, ms));

const fetchUser = (id) => wait(50).then(() => ({ id, name: 'ホン' }));

const fetchOrders = (id) => wait(50).then(() => [{ orderId: 1 }]);

code
const fetchProfile = (id) => wait(50).then(() => ({ bio: '開発者' }));

(async () => {
    // 🏎️ 3つのリクエストを"同時"開始 → 全て終わるまで待機 → 結果を配列で受け取る
    const [user, orders, profile] = await Promise.all([
        fetchUser(42),     // 開始時刻: 0ms
        fetchOrders(42),   // 開始時刻: 0ms  ← 一緒に出発!
        fetchProfile(42)   // 開始時刻: 0ms
    ]);
    // 終了時刻: max(各リクエスト所要時間)

    console.log(user);     // { id: 42, name: 'ホン' }
    console.log(orders);   // [{ orderId: 1 }]
    console.log(profile);  // { bio: '開発者' }
})();

3つのリクエストを同時に開始 → すべて完了した時点で結果を返す。直列の3倍高速

注意: 一つでも失敗すると全体が失敗部分的な失敗を許容する場合はPromise.allSettledを使用:

javascript
const results = await Promise.allSettled([...]);
// [{status:'fulfilled', value:...}, {status:'rejected', reason:...}]

Promise.race — 最速の一つ

javascript
// 🧪 偽の応答 — 速い(50ms) vs 遅い(200ms)
const wait = (ms, val) => new Promise(r => setTimeout(() => r(val), ms));

(async () => {
    // 🏁 race = 競争。両者のうち"先に終わったもの"だけを結果として受け取る
    const fastest = await Promise.race([
        wait(50,  'cached'),    // 速い → 勝ち
        wait(200, 'origin'),    // 遅い → 負け
    ]);
    console.log(fastest);   // 'cached'   ← 50msの応答が先

    // 💡 応用 — タイムアウト実装
    const withTimeout = (promise, ms) => Promise.race([
        promise,
        new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), ms))
    ]);

    try {
        const slow = wait(500, '遅い応答');
        const res = await withTimeout(slow, 100);   // 100ms以内に応答がなければtimeout
        console.log(res);
    } catch (e) {
        console.error(e.message);   // "timeout"   ← 500 > 100 なので時間超過
    }
})();

よくある落とし穴

1. catchの漏れ:

javascript
fetch('/api').then(handle);   // ❌ エラー無視
fetch('/api').then(handle).catch(err => log(err));   // ✅

処理されていないPromise rejectionはUnhandled Promise Rejection警告を出す。必ずcatchすること

2. then内でさらにthenをネスト:

javascript
// ❌ Promise がネスト
fetch('/api')
    .then(res => {
        res.json().then(data => console.log(data));
    });

// ✅ return でチェイニング
fetch('/api')
    .then(res => res.json())
    .then(data => console.log(data));

3. awaitの代わりにthenを使いすぎる:
現在はasync/awaitが標準。thenチェーンはシンプルなケースのみ

まとめ

  • Promise = 将来の結果を約束するオブジェクト
  • then·catch·finallyチェーン
  • Promise.allで並列処理
  • モダンなコードはasync/awaitでよりすっきりと

⚡ 実際に試してみよう — Promiseチェーン + Promise.all

逐次チェーンと並列Promise.allを実際に比較してみましょう。
✏️ JS 코드
📟 コンソール出力
▶ 実行ボタンを押してください
⚠️ ブラウザのサンドボックスで実行 — console.log()のみ対応、alert/fetchは不可

🤖 AIにこう依頼してみよう

このlessonの概念を理解していれば、AIに具体的に指示できます。漠然とした「直して」ではなく、語彙のある依頼 — それがトークン節約の出発点です。

  • 「この.thenチェーンをasync/awaitに書き換えて」
  • 「このコードのunhandled rejectionの可能性を.catch / try-catchで防いで」
  • 「これらのfetchをPromise.allで並列化して」

なぜこれがトークンを節約するのか

概念を知らないと、AIの回答を受け取っても「それって何ですか?」とまた聞き直す必要があります。その「聞き直し」がトークンを消費します。概念を一度習得しておけば、会話が一度で終わります。

先に読むとよい概念: イベントループ
次のおすすめ: Async/Await
Promise - JavaScript