🤝

JSのズッコケポイントPromiseを作ってみて理解してみる

2025/02/09に公開

概要

JavaScriptにおけるPromiseって全然何やってるかわからなくないですか?(私だけです・・・?)
こうやって動くもんだからこうやって書くんだよ!と学びたての頃教えられてほーんそうかといった具合で今の今まで来てしまったのですが、やはりちゃんと理解していないと複雑なことをやろうとすると詰まるわけで・・・ということなので簡易的にPromiseを作ってみて大体こんなことやってんだ、
だからそういう動きになるんだを見ていこうかなと思います!(Promiseに困っている同士を助けられたらなと・・・!)

学べること

今回の自作Promiseで学べることとしては

  1. 意外と処理自体はシンプル、状態を管理してその状態に応じて処理をしている(then、catch)
  2. resolveとかrejectとかややこしいけどPromiseが用意してくれるから気にしすぎなくてOK!また、名前がイカついから難しそうだけどやってることとしては状態を成功にするか失敗にするかだよ
  3. Promiseを使う側としてやることは、Promiseさせたい処理の用意、成功した場合の処理、失敗した場合の処理だよ

この3点がなんとなーくゆるーく理解できればと思います

そもそもPromiseってどういう時使うやつ・・・?

いわゆる非同期処理と言われるものを実装したい時に使用します
基本的には上から下に処理が流れますが、非同期処理にしたい処理、1例を出すとAPIの呼び出しのような処理は呼び出す時点で次の処理に流れてしまいます。
ですが実際にやりたいこととしてはAPIの実行結果を待ってから後続の処理を行うになると思うのでそう言った場合に、「ある特定の処理が終わったら次の処理を行う」の定義に使うものがPromiseに当たります

早速作ってみよう

御託はいいんだ!ということで早速作ってみます!実際はもっと複雑な動きをしているのですがここでは本当に重要な部分だけを実装して動きを確認していこうと思います

resolveだけのシンプルなものを作ってみよう!

全体像は下記の通りになります!

class MyPromise {
  constructor(executor) {
    // 🔄 状態管理: "pending" → "fulfilled"(成功) or "rejected"(失敗)
    this.state = "pending";  // 🔹 初期状態は「保留」
    this.value = undefined;  // 🎁 成功時の値を保存
    this.onSuccess = null;   // ✅ 成功時のコールバック関数を保存

    console.log("🚀 Promise が作成された!");

    // ✅ resolve() 関数の定義(成功時に呼ばれる)
    const resolve = (value) => {
      console.log("✅ resolve() が呼ばれた!");

      if (this.state === "pending") {
        this.state = "fulfilled";  // 🎉 状態を「成功」に変更!
        this.value = value;        // 🎁 成功時の値を保存

        // 🔥 もし then() が先に登録されていたら、ここで実行!
        if (this.onSuccess) {
          console.log("🔥 then() の登録済みコールバックを実行!");
          this.onSuccess(value);
        }
      }
    };

    // 🎬 executor 関数を実行し、resolve() を渡す
    executor(resolve);
  }

  // ✅ then() メソッド(成功時の処理を登録)
  then(onSuccess) {
    console.log("📌 then() が登録された!");
    this.onSuccess = onSuccess;

    // 🔄 もしすでに resolve() されていたら、即時実行!
    if (this.state === "fulfilled") {
      console.log("🔄 すでに resolve 済みなので即実行!");
      onSuccess(this.value);
    }
  }
}

// ✅ 動作確認: 1秒後に resolve() を実行する
const main = () => {
  console.log("🎬 main() 実行開始!");

  const myPromise = new MyPromise((resolve) => {
    setTimeout(() => {
      console.log('Promiseさせたい処理だよ!')
      resolve("🎉 1秒後に成功!");
    }, 1000);
  });

  // ✅ then() を登録(成功時の処理を指定)
  myPromise.then((result) => {
    console.log("✅ then の実行結果:", result);
  });

  console.log("🎬 main() の処理終了!");
};

// 🚀 実行
main();

「え、ながっ・・・、何がシンプルなんだよ・・・」って思いますよね?私も思います
コードとしてはシンプルといいつつ長めになってしまっているのですがやっていることとしては下記の3つだけになります

  1. 処理の状態管理をする
  2. 関数の登録をする
  3. 状態に合わせて処理を実行する

一つ一つ見て行けたらなと思います!

処理の状態管理をする

Promiseを使われたことは何となく「成功したらthen!失敗したらcatch!」
のイメージがあると思います
よしなに動いてくれるんだなーと思っていたのですが、
じゃあなんで成功したら「then」失敗したら「catch」が実行されるんだ?というと少し難しいですよね
実態としては、処理の状態を管理し、状態に合わせて条件分岐で処理を行っているからになります
・・・意外とシンプルだなと思いました
resolveしか現状は実装していないので、成功のパターンしかありませんが
resolveに入ったら状態が成功になるよ、成功になったことによってthenでの処理が実行されるよ(成功以外は条件に入らないから何も実行されないよ)といった具合になります!
(もしかしたら混乱させることかもしれないですが、thenもcatchも成功しても失敗してもメソッド自体は呼ばれているってことですね、呼ばれはするが、何も実行されずにreturnされるが正しい表現ですね🙌)

関数の登録をする

では、実際にPromiseで使う関数を登録しましょう
押さえておくポイントとしてはこの3点で

  1. Promiseさせたい処理を用意する
  2. resolve、つまり成功を通知する処理を記載する1の処理に追加する
  3. resolveとrejectはPromise側で用意してくれる

まず、1の内容を確認していきます。ここでは実際にPromiseさせたい処理を用意します!
例えば、今回の例で言うとここですね(段階を踏むためresolveの記載は削除しています)

  const myPromise = new MyPromise(() => {
    setTimeout(() => {
      console.log('Promiseさせたい処理だよ!')
    }, 1000);
  });

処理の内容としては、Promiseさせたい処理だよ!とログに吐き出す処理になっていて
・Promiseさせたい処理 = setTimeoutを使いconsoleにログを1秒後に出力する
と言った形になっています

そして、次に「resolve」成功したことをPromiseに通知する処理を追記します

  // 引数にresolve(名前は何でもいいですが慣習的にresolveにしています)
  const myPromise = new MyPromise((resolve) => {
    setTimeout(() => {
      console.log('Promiseさせたい処理だよ!')
      // 処理が成功したよ!と言うことをPromiseに伝えるためresolveを呼び出す
      resolve("🎉 1秒後に成功!");
    }, 1000);
  });

私はこの時点ですら少し複雑になったな・・・と思いました
ただ、今は成功したことをPromiseに伝えなければいけない、そのためにresolveという関数を呼び出しているということに注目していてください、resolveに関してはこの後説明を記載します

そして、3番目の「resolveとrejectはPromise側で用意してくれる」ですが、
ここまで一旦Promiseの用意自体は完了しています(まだthenなどの記載はしていませんが!)
ただ概念として知っておいた方が、理解につながる箇所があります

まず、Promiseはインスタンスを作成した際にコンストラクタでresolveとrejectという関数を用意し、executor(Promiseに渡された関数)を用意したresolveとrejectを用いて実行します

おいおい何言ってんだいきなり早口で色々言い出したよと思いましたよね?
そうですね、私も最初何が何だかわかっていなかったのですが、今回はあくまで概念理解を念頭に置いているのでめちゃくちゃに咀嚼して言うと

⭐️resolve(成功を知らせる関数)とreject(失敗を知らせる関数)はPromise側が勝手に用意するよ!
❤️用意しておくから、実際にPromiseさせたい処理の引数としてresolveとrejectを事前に渡しておいてね
♠️また成功を知らせたい場合は、resolve 失敗を知らせたい場合は reject を呼び出してね!

大体こんな感じかなと思います、上記の3つを見ながらコード見てみましょう!
マークが該当の処理に対応しています!

  constructor(executor) {
    // 🔄 状態管理: "pending" → "fulfilled"(成功) or "rejected"(失敗)
    this.state = "pending";
    this.value = undefined;
    this.onSuccess = null;

    console.log("🚀 Promise が作成された!");

    // ⭐️ resolveをPromise側が用意してくれている!
    const resolve = (value) => {
      console.log("✅ resolve() が呼ばれた!");

      if (this.state === "pending") {
        this.state = "fulfilled";
        this.value = value;

        if (this.onSuccess) {
          this.onSuccess(value);
        }
      }
    };

    // 🎬 executor 関数を実行し、resolve() を渡す
    executor(resolve);
  }

// ~~~その他コード省略~~~
const main = () => {
  console.log("🎬 main() 実行開始!");

  // ❤️ resolveを事前に渡しておいてね
  const myPromise = new MyPromise((resolve) => {
    setTimeout(() => {
      console.log('Promiseさせたい処理だよ!')
      // ♠️ 成功を知らせたいから呼び出す!
      resolve("🎉 1秒後に成功!");
    }, 1000);
  });

  myPromise.then((result) => {
    console.log("✅ then の実行結果:", result);
  });

  console.log("🎬 main() の処理終了!");
};

上記さえ押さえておけば、基本的な流れは掴めるのかなと思います!

状態に合わせて処理を実行する

実際にPromiseを用意できました!
では、実際に実行してみましょう!・・・とはいかず、実際に成功した時の処理を用意しないといけませんね、今まで用意したものをさくっと振り返りながらやってみます

  1. Promise内で状態管理をしている
  2. 成功用の関数を用意した

この2点をやったと思うのですが、ここからは単純です
成功用の関数(Promise側)が用意してくれている関数で、Promise内の状態を"fulfilled"つまり成功に変更してくれているので、成功の時のみ処理を実行する「then」と言う関数を作ります

  // ✅ then() メソッド(成功時の処理を登録)
  then(onSuccess) {
    // 成功している場合に実行したい関数を登録(thenの引数に渡す関数)
    this.onSuccess = onSuccess;

    // 🔄 Promise内の状態が"fulfilled"つまり成功だった場合、関数を実行する
    if (this.state === "fulfilled") {
      console.log("🔄 すでに resolve 済みなので即実行!");
      onSuccess(this.value);
    }
  }

// ~~~その他コード省略~~~
const main = () => {
  console.log("🎬 main() 実行開始!");

  const myPromise = new MyPromise((resolve) => {
    setTimeout(() => {
      console.log('Promiseさせたい処理だよ!')
      resolve("🎉 1秒後に成功!");
    }, 1000);
  });

  // ここで成功した場合に呼び出したい関数をthenに引数として渡す!
  myPromise.then((result) => {
    console.log("✅ then の実行結果:", result);
  });

  console.log("🎬 main() の処理終了!");
};

thenとかcatchって魔法みたいなことをやっているのかと思っていましたが、すごいシンプルですね
実際に作ってみると確かにこれでいけるよな・・・とは思いますが🪄

実際に動かしてみる

一番最初に紹介したコードから今回の理解に当たるには不要なlogを外したものを再度定義します

class MyPromise {
    constructor(executor) {
      // 🔄 状態管理: "pending" → "fulfilled"(成功) or "rejected"(失敗)
      this.state = "pending";  // 🔹 初期状態は「保留」
      this.value = undefined;  // 🎁 成功時の値を保存
      this.onSuccess = null;   // ✅ 成功時のコールバック関数を保存
  
      // ✅ resolve() 関数の定義(成功時に呼ばれる)
      const resolve = (value) => {
        if (this.state === "pending") {
          console.log(`更新前の${this.state} の状態`);
          this.state = "fulfilled";  // 🎉 状態を「成功」に変更!
          console.log(`更新後の${this.state} の状態`);
          this.value = value;        // 🎁 成功時の値を保存
          
          if (this.onSuccess) {
            this.onSuccess(value);
          }
        }
      };
  
      // 🎬 executor 関数を実行し、resolve() を渡す
      executor(resolve);
    }
  
    // ✅ then() メソッド(成功時の処理を登録)
    then(onSuccess) {
      this.onSuccess = onSuccess;
  
      if (this.state === "fulfilled") {
        onSuccess(this.value);
      }
    }
  }
  
  // ✅ 動作確認: 1秒後に resolve() を実行する
  const main = () => {
    const myPromise = new MyPromise((resolve) => {
      setTimeout(() => {
        resolve("🎉 1秒後に成功!");
      }, 1000);
    });
  
    // ✅ then() を登録(成功時の処理を指定)
    myPromise.then((result) => {
      console.log(`${myPromise.state} の状態でthenが実行された`);
      console.log("✅ then の実行結果:", result);
    });
  };
  
  // 🚀 実行
  main();

実際に実行してみます!

> 更新前のpending の状態
> 更新後のfulfilled の状態
> fulfilled の状態でthenが実行された
> ✅ then の実行結果: 🎉 1秒後に成功!

このようなログが出てきました!実施に何が起きたのかをもう一度確認しましょう

  1. 更新前のpending の状態 → resolveが実行された時のPromiseの状態
  2. 更新後のfulfilled の状態 → resolveが実行された後のPromiseの状態
  3. fulfilled の状態でthenが実行された → thenが実際に呼び出された時の状態
  4. ✅ then の実行結果: 🎉 1秒後に成功! → thenに渡した成功したい時の処理が実際に動いた!

結論

冒頭にも記載しましたが、意外と処理自体はシンプルですね
ただ、大幅に説明を省いている部分もあります
resolve関数内にある下記の処理など

    // 🔥 もし then() が先に登録されていたら、ここで実行!
    if (this.onSuccess) {
      console.log("🔥 then() の登録済みコールバックを実行!");
      this.onSuccess(value);
    }

ここは一旦基本の理解には個人的には省いてしまってもいい箇所なのかなと思って言及していません(書く気力がなくなったなどでは決してありません・・・?)
また、rejectも実装はしていません、ですがthenの処理の流れを理解している方はrejectの実装はさほど難しくないのかと思います!(thenの状態判定を失敗の時、処理を実行すると言うバージョンを定義するイメージです!)

とにかく、

  1. 状態管理を行って実行する処理・実行しない処理を判断している(then)
  2. resolve, rejectは勝手にPromiseが用意してくれる
  3. resolve, rejectは名前がイカついけど要は、成功したのか失敗したのかを伝え状態を更新しに行く関数
  4. Promiseを使う側としてやることは、Promiseさせたい処理の用意、成功した場合の処理(then)、失敗した場合の処理(catch)

と言うことがなんとなーくわかればよいなーと思っております🙏

Discussion