class MyPromise {
  constructor(executor) {
    // 関数以外の引数はエラーを出す
    if (!(executor instanceof Function)) {
      throw new Error("Require a function to init MyPromise");
    }
    // 初期ステートをpending、値をundefinedに
    this.state = "pending";
    this.value = undefined;
    this.onFulfilledQueue = [];
    this.onRejectedQueue = [];

    // executor関数を実行、ここでコンテキストのthisをプロミスインスタンスにバインドする
    try {
      executor(this.resolve.bind(this), this.reject.bind(this));
    } catch (e) {
      this.reject(e);
    }
  }

  then(onFulfilled, onRejected) {
    onFulfilled =
      onFulfilled instanceof Function ? onFulfilled : (value) => value;
    onRejected =
      onRejected instanceof Function
        ? onRejected
        : (value) => {
            throw value;
          };

    const { value, state } = this;
    return new MyPromise((resolveNext, rejectNext) => {
      const onFulfilledFn = () => {
        try {
          const res = onFulfilled(value);
          this._resolvePromise(res, resolveNext, rejectNext);
        } catch (e) {
          rejectNext(e);
        }
      };

      const onRejectedFn = () => {
        try {
          const res = onRejected(value);
          this._resolvePromise(res, resolveNext, rejectNext);
        } catch (e) {
          rejectNext(e);
        }
      };

      switch (state) {
        case "fulfilled":
          onFulfilledFn(value);
          break;
        case "rejected":
          onRejectedFn(value);
          break;
        case "pending":
          //ここはエラー処理付きのバージョンをプッシュ
          this.onFulfilledQueue.push(onFulfilledFn);
          this.onRejectedQueue.push(onRejectedFn);
          break;
      }
    });
  }

  catch(onRejected) {
    return this.then(null, onRejected);
  }

  finally(callback) {
    return this.then(
      (value) => MyPromise.resolve(callback()).then(() => value),
      (err) =>
        MyPromise.resolve(callback()).then(() => {
          throw err;
        })
    );
  }

  static resolve(value) {
    return new MyPromise((resolve) => {
      resolve(value);
    });
  }

  static reject(value) {
    return new MyPromise((resolve, reject) => {
      reject(value);
    });
  }

  _resolvePromise(res, resolve, reject) {
    if (res instanceof MyPromise) {
      res.then(resolve, reject);
    } else {
      resolve(res);
    }
  }

  resolve(val) {
    if (this.state === "pending") {
      this.state = "fulfilled";
      this.value = val;
      // 配列に未実行の関数が存在する場合、先頭から取り出して実行
      while (this.onFulfilledQueue.length) {
        this.onFulfilledQueue.shift()(val);
      }
    }
  }

  reject(val) {
    if (this.state === "pending") {
      this.state = "rejected";
      this.value = val;
      while (this.onRejectedQueue.length) {
        this.onRejectedQueue.shift()(val);
      }
    }
  }

  static race(promises) {
    return new MyPromise((resolve, reject) => {
      for (let [idx, promise] of promises.entries()) {
        // もしプロミスインスタンスではない場合、一回プロミスに変換する
        if (!(promise instanceof MyPromise)) {
          promise = MyPromise.resolve(promise);
        }
        // 次にthenを呼び出す
        promise.then(
          (res) => {
            resolve(res);
          },
          (err) => {
            reject(err);
          }
        );
      }
    });
  }

  static all(promises) {
    return new MyPromise((resolve, reject) => {
      let results = [];
      let count = 0;
      for (let [idx, promise] of promises.entries()) {
        // もしプロミスインスタンスではない場合、一回プロミスに変換する
        if (!(promise instanceof MyPromise)) {
          promise = MyPromise.resolve(promise);
        }
        // 次にthenを呼び出す
        promise.then(
          (res) => {
            // 結果はプロミスの投入順番と対応するためindex必須
            results[idx] = res;
            count++;
            // すべてのプロミスがfulfilledになれば、結果配列をリターン
            if (count === promises.length) {
              resolve(results);
            }
          },
          (err) => {
            // どれか一つのプロミスが失敗したら、全体も失敗
            reject(err);
          }
        );
      }
    });
  }
}

console.log("create new promise");

let p = new MyPromise((resolve, reject) => {
  console.log("constructor callback");
  resolve("promise value");
});

p.then((res) => {
  console.log(res); // promise value
})
  .then((res) => {
    console.log(res); // undefined
    return 123;
  })
  .then((res) => {
    console.log(res); // 123
    return new MyPromise((resolve) => {
      resolve(456);
    });
  })
  .then((res) => {
    console.log(res); // 456
    return "return value";
  })
  .then()
  .then("string")
  .then((res) => {
    console.log(res); // return value
    throw new Error("error occurred!");
  })
  .then((res) => {
    console.log(res); // 出力なし
  })
  .catch((err) => {
    console.error(err.message); // error occurred!
    return "error!";
  })
  .then((res) => {
    console.log("after catch " + res); // after catch error!
    return 789;
  })
  .finally((res) => {
    console.log(res); // undefined
    console.log("done!"); // done!
  });

console.log("chain ends");

MyPromise.resolve(100).then(console.log);

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.