JavaScript | 非同期処理:Promise 基礎 – Promise とは何か

JavaScript JavaScript
スポンサーリンク

まず Promise を一言でイメージする

Promise は、
「まだ終わっていない非同期処理の“結果がそのうち入る箱”」だと思ってください。

今は結果が分からないけど、

  • そのうち 成功するかもしれない
  • もしかしたら 失敗するかもしれない

どちらにしても、「結果が決まったら知らせてね」と約束してくれる箱。
それが Promise です。

ここが重要です。
Promise 自体は「非同期処理そのもの」ではなく、
非同期処理の「結果」とその「状態」を表現するためのオブジェクト です。


Promise の3つの状態(これが本当に大事)

pending / fulfilled / rejected という3段階

Promise には、必ずどれか一つの「状態」があります。

作られた直後は pending(保留中)
非同期処理がうまくいくと fulfilled(成功)
エラーなどで失敗すると rejected(失敗)

一度 fulfilled か rejected になると、その後は状態が変わりません(結果が確定するイメージです)。

ここが重要です。
Promise は「まだ結果が決まっていない(pending)」→「成功 or 失敗のどちらかに“確定”する」
この流れを、オブジェクトとして表現したものです。

ざっくりコードで見る

const p = new Promise((resolve, reject) => {
  // ここは実行された瞬間(同期)に走る

  const success = true; // 仮に

  if (success) {
    resolve("成功した結果");
  } else {
    reject(new Error("失敗した理由"));
  }
});
JavaScript

このとき、

  • new Promise(...) した瞬間 → p は pending
  • resolve("成功した結果") が呼ばれた時 → p は fulfilled 状態になり、値は “成功した結果”
  • もし reject(...) が呼ばれたら → p は rejected 状態になり、エラー情報を持つ

というイメージになります。


resolve / reject と then / catch の関係

Producer(作る側)と Consumer(使う側)

Promise を理解するときは、「作る側」と「使う側」を分けて考えるとスッキリします。

作る側(Producer):
new Promise((resolve, reject) => { ... }) の中で、非同期処理をして、終わったら resolve または reject を呼ぶ役。

使う側(Consumer):
.then(...).catch(...) を使って、「成功したときに何をするか」「失敗したときに何をするか」を登録する役。

Promise はこの「作る側」と「使う側」をゆるくつなぐ接着剤のようなものです。

then / catch で「結果が決まったあと」の処理を書く

const p = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("OK"); // 1秒後に成功
  }, 1000);
});

p.then((value) => {
  console.log("成功:", value);
}).catch((error) => {
  console.log("失敗:", error);
});
JavaScript

流れを丁寧に追うとこうなります。

Promise 作成時点では pending。
1秒後、resolve("OK") が呼ばれる → 状態が fulfilled に変わり、値は “OK” に確定。
そのタイミングで .then(...) に登録していたコールバックが「非同期的に」呼ばれる。
もし reject(...) が呼ばれていたなら、.catch(...) に登録したコールバックが呼ばれる。

ここが重要です。
Promise の then / catch は、「結果が決まったときに呼ばれるコールバックを登録する場所」 です。
コールバック地獄との違いは、「箱」を介して直線的につなげやすくなっているところです。


一番シンプルな例:すぐ成功する Promise

即時 resolve の例

const p = new Promise((resolve, reject) => {
  resolve(42); // すぐに成功
});

p.then((value) => {
  console.log("value:", value); // 42
});
JavaScript

このときのポイントは、
resolve をすぐに呼んでいても、then のコールバックは 同期的には実行されない ことです。

イメージとしては、

Promise を作る → すぐに fulfilled になる
→ 「じゃあこの Promise に登録されている then のコールバックを、“マイクロタスクキュー”に積んでおこう」
→ 「現在の同期処理(グローバルコード)が終わったあと」でコールバックが実行される

という流れです。

なので、次のコードはこう動きます。

const p = new Promise((resolve) => {
  resolve("OK");
});

console.log("A");

p.then((v) => {
  console.log("B:", v);
});

console.log("C");
JavaScript

出力順は、

A
C
B: OK

になります。

ここが重要です。
Promise.then の中身は「非同期コールバック」として扱われる
これを覚えておくと、「なぜこの順番で動くの?」という疑問が減ります。


非同期処理を Promise でラップするイメージ

setTimeout を Promise 化する例

よくある練習として、「タイマーを Promise でラップする」というものがあります。

function wait(ms) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(); // ms ミリ秒後に成功
    }, ms);
  });
}

wait(1000).then(() => {
  console.log("1秒経ちました");
});
JavaScript

ここで起きていることを分解すると、

  1. wait(1000) を呼ぶと、「1秒後に resolve される Promise」が返ってくる
  2. .then(...) は、「その Promise が fulfilled になったときに実行される処理」を登録する
  3. 1秒後に resolve() → Promise の状態が fulfilled に変わる
  4. イベントループが .then のコールバックを実行する → "1秒経ちました" と表示

という流れになります。

つまり、
「時間がかかる処理」を Promise 化して、「終わったら then で続きやるね」と書ける ようになるわけです。

fetch(HTTP通信)も Promise を返す

実務では、fetch などの API がすでに Promise を返してくれます。

fetch("/data.json")
  .then((response) => response.json())
  .then((data) => {
    console.log("取得したデータ:", data);
  })
  .catch((error) => {
    console.error("通信に失敗:", error);
  });
JavaScript

ここでも、

fetch が返す Promise が「通信の成功/失敗」を表す箱
.then で「成功時にしたいこと」をチェーン
.catch で「失敗したときにしたいこと」をまとめて処理

という流れになっています。

ここが重要です。
Promise があるおかげで、「非同期処理の結果」を一つのオブジェクトとして扱い、then/catch で読みやすく継続処理を書ける ようになっています。


Promise の「チェーン」という強力な性質

then は「次の Promise を返す」

Promise の大事な特徴の一つは、.then が「新しい Promise を返す」ことです。

doAsync1()
  .then((result1) => {
    return doAsync2(result1); // ここで返した値が「次の Promise」
  })
  .then((result2) => {
    return doAsync3(result2);
  })
  .then((result3) => {
    console.log("全部成功:", result3);
  })
  .catch((err) => {
    console.error("どこかで失敗:", err);
  });
JavaScript

このように書くと、

非同期1 → 非同期2 → 非同期3

という順番の処理を、
ネストせずに上から順に読める形で書ける ようになります。

ここが重要です。
Promise の本当の強みは、「then で次々と非同期処理を“直線的に”つなげられること」 です。
コールバック地獄の「右にズレるピラミッド」を、「縦に読めるストーリー」に戻してくれるわけです。


初心者として「Promise とは何か」をこう理解しておくと楽になる

長くなったので、いったん整理します。

Promise を理解するうえで、今の段階でしっかり持っておいてほしいイメージはこれです。

Promise は、「非同期処理の結果」を入れておくための 箱(オブジェクト)

その箱は、最初は pending(保留)だが、いつか fulfilled(成功)か rejected(失敗)のどちらかに「確定」する。

resolve は「成功しました」と箱に結果を入れる操作、reject は「失敗しました」とエラー情報を入れる操作。

.then は「成功したときに実行する処理を登録する場所」、.catch は「失敗したときの処理を登録する場所」。

.then は新しい Promise を返すので、非同期処理を「上から下に」チェーンして書ける。

ここが重要です。
Promise そのものを完璧に暗記する必要はありません。
「非同期処理の結果を表す箱で、then/catch で“後続の処理”をつなげる仕組み」として、まずはざっくり掴んでおけば十分です。


これからの練習のすすめ方

Promise を自分のものにするには、実際に少し触ってみるのが一番です。
例えば、こんな順番で練習してみてください。

  1. new Promise で「1秒後に resolve される Promise」を自分で書く
  2. それに .then をつないで、「1秒後にログを出す」コードを書く
  3. setTimeout をラップした wait(ms) 関数を作り、.then で処理をつなげてみる
  4. wait(1000).then(...) を2回チェーンして、「1秒+1秒後」に処理が走るようにしてみる

このあたりを自分の手で書いてみると、
Promise は「難しい呪文」ではなく、「ただの箱+then/catch」という道具 だと実感できてきます。

そこまで行ったら、次は

  • Promise を返す関数を作る
  • fetch のような既存 API の Promise を使いこなす

に進んでいくと、とても良い流れで「Promise の基礎」から応用まで繋がっていきます。

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