【小ネタ】JavaScriptのPromiseに関する実行順序を確認する

# はじめに
Promiseオブジェクト生成時に渡す関数（executor）っていつ実行されるんだっけ、とか、executor完了後に「.then()」「.error()」でコールバック関数を登録した場合ってどうなるんだっけ、という点が個人的に曖昧だったので整理しました。
関連情報、および確認用のコードをまとめます。

Promise自体の説明については、以下のドキュメントが丁寧で分かりやすいと思います。

JavaScript Promiseの本
https://azu.github.io/promises-book/

公式はこちら。

Promise - JavaScript | MDN
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

# Promiseの基本形／用語
Promiseに関わる要素のうち、明示的な呼称が無いものが存在します。
本稿では各要素について下記のように呼称することとします。

```javascript:Promiseの基本形
// Promiseオブジェクト生成関数を定義する
const createPromise = () => {
  return new Promise((resolve, reject) => { // 本稿ではこの関数を executor と呼ぶ。
    // executor は2つの引数を持ち、その内容はいずれも関数。
    // 本稿ではそれぞれそのまま resolve(), reject() と呼ぶ。
    // 
    // executor 内の処理が正常終了した場合は第1引数である resolve() を、異常終了した場合は第2引数である reject() を実行するよう実装する。

    try {
      // なにか処理を実行する
      // ...
      // ...
      
      // executor 内の処理が正常終了したときに以下のように実行
      // ここで渡した引数は、Promiseオブジェクトの「.then()」に渡す関数内から、第1引数として参照できるようになる
      resolve('This executor is resolved.');
    } catch (e) {
      // executor 内の処理が異常終了したときに以下のように実行
      // ここで渡した引数は、Promiseオブジェクトの「.catch()」に渡す関数内から、第1引数として参照できるようになる
      reject('This executor is rejected.');
    }
  });
};


// Promiseオブジェクトを生成し、実行する
const promiseObject = createPromise();

// executor 実行完了後の処理をハンドリングする
promiseObject
  .then((resolvedValue) => { // 本稿ではこの関数を onFullFilled関数 と呼ぶ。
    // executor の処理が正常終了している場合、この関数内の処理が実行される。
    // この関数の第1引数（この例ではresolvedValue）には、executor 内で実行された resolve() に渡された値が渡される。
    // つまりこの例では 'This executor is resolved.' が入っている。

  })
  .catch((rejectedValue) => { // 本稿ではこの関数を onRejected関数 と呼ぶ。
    // executor の処理が異常終了している場合、この関数内の処理が実行される。
    // この関数の第1引数（この例ではrejectedValue）には、executor 内で実行された reject() に渡された値が渡される。
    // つまりこの例では 'This executor is rejected.' が入っている。

  });

```

上記コードから用語を抜き出すと下記のようになります。

| 用語 | 説明／内容 | 備考 |
|------|-----------|------|
| Promiseオブジェクト生成関数 | 上記コードの createPromise() 関数。 | |
| executor | Promise生成時に第1引数として渡す関数。 | MDNドキュメント上も executor と呼ばれている。 |
| resolve() | executorの第1引数。通常、executorが正常終了した場合に実行されるよう実装する。 | MDNドキュメント上は resolutionFunc と呼ばれている。 |
| reject() | executorの第2引数。通常、executorが異常終了した場合に実行されるよう実装する。 | MDNドキュメント上は rejectionFunc と呼ばれている。 |
| Promiseオブジェクト | 上記コードの promiseObject が該当。 | |
| onFullFilled関数 | Promiseオブジェクトの .then() メソッドの第1引数として渡される関数。 executorが正常終了している場合、つまりresolve()の実行が完了したのちに実行される処理が渡される。 | |
| onRejected関数 | Promiseオブジェクトの .catch() メソッドの第1引数として渡される関数。 executorが異常終了している場合、つまりreject()の実行が完了したのちに実行される処理が渡される。 | |


# 確認したかったこと
- Promise生成時に渡す関数（executor）はいつ実行されるか
    - Promiseオブジェクトを生成した時点で実行される
- Promiseの「.then()」「.error()」メソッドによる、onFullFilled関数／onRejected関数の登録が完了する前に、executorの実行が完了した場合、どうなるか
    - executorは特に問題なくそのまま完了し、PromiseオブジェクトはSettled(FullFilled / Rejectedのいずれか)の状態になる。
    - Promiseオブジェクトには、executor内のコールバック関数（つまりresolve() / reject()）に渡された値が保持される。
    - 「.then()」「.error()」メソッドにてonFullfilled関数／onRejected関数が登録されたのち、登録されたコールバック関数は非同期で即座に実行される。
    - onFullFilled関数／onRejected関数は、Promiseオブジェクトに紐づけられた値を参照しながら、特に問題なく処理を実行し完了する。

# 確認した事項
## Promise生成時に渡す関数（executor）はいつ実行されるか

executorはPromiseオブジェクト生成中に実行されるとのことです。
ということは、Promiseオブジェクトの生成が完了したときには、すでにexecutorは実行を開始しているということです。

> **Constructor Syntax**
> ```var promiseObj = new Promise(executor);```
> **Arguments**
> **executor**
> A function to be executed by the constructor, during the process of constructing the promiseObj.  The executor is custom code that ties an outcome to a promise. You, the programmer, write the executor.  The signature of this function is expected to be:

Promise - JavaScript | MDN
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#Constructor_Syntax

## onFullFilled関数／onRejected関数がいつ実行されるか

「.then()」「.error()」で登録されたコールバック関数は、登録直後に非同期で実行される。

JavaScript Promiseの本 - 2.3. コラム: Promiseは常に非同期?
https://azu.github.io/promises-book/#promise-is-always-async

## onFullFilled関数／onRejected関数の登録前に、executorの実行が完了した場合、どうなるか

端的に説明した記述が見当たらなかったので、次のサンプルコードで確認しました。
結論としては、executor内でresolve()若しくはreject()を実行した際の引数を、onFullFilled関数／onRejected関数内から参照することができ、問題なく処理が完了します。

# 確認用サンプルコード
## コード
以下のようなサンプルコードで確認しました。

```javascript:確認用サンプルコード

// Promise オブジェクトを返却する関数 createPromise を作成する
const createPromise = () => {
  return new Promise((resolve, reject) => {
    // executor 実行時の時刻を出力する。
    console.log(
      "2. At the top of the executor() of a Promise object. at " + Date.now()
    );

    // 後続の処理を実行し、最後にresolve()を実行する。
    // 一般的なユースケースとあわせるため、100ミリ秒後に後続の処理が完了することとした。
    // ただし、Promise.then() / .error() によるコールバック関数登録前に完了する。
    setTimeout(() => {
      console.log(
        "5. At the function called delayed within the executor(). at " + Date.now()
      );
      
      // executor内の関数でresolve()が実行された時刻が分かるよう、UNIXエポックミリ秒を引数に渡す。
      resolve({message: "The executor has finished.", unixtime: Date.now(),});
      
      // reject()実行時の動作確認用。
      // reject({message: "The executor has failed.", unixtime: Date.now(),});

    }, 100);
  });
};

// createPromise 関数を実行し、Promiseオブジェクトを生成する。
console.log("1. Create a Promise object. at " + Date.now());
let promiseObject = createPromise();
console.log("3. A Promise object has been created. at " + Date.now());

// Promise.then() / .error() によりコールバック関数を登録する。
// コールバック関数の登録は、400ミリ秒後に完了する
setTimeout(() => {
  console.log("6. Registering the onFullFilled and onRejected callback functions has been started. at " + Date.now());
  promiseObject
    .then((resolvedValue) => {  // onFullFilled関数
      console.log(
        "7. The onFullFilled function has finished. " + JSON.stringify(resolvedValue) + " at " + Date.now()
      );
    })
    .catch((rejectedError) => {  // onRejected関数
      console.log(
        "7. The onRejected function has finished. " + JSON.stringify(rejectedError) + " at " + Date.now()
      );
    });
}, 400);

console.log("4. At the end of this program. at " + Date.now());

```

## 実行結果
実行結果は下記のとおりです。
処理開始から102ミリ秒後にexecutor関数が完了しています。（ここで言う「完了」とは、resolve()若しくはreject()が実行されたことを指します。）
executor内で最終的にresolve()が実行されたのは103ミリ秒経過後であることが、7行目のログから分かります。

その後、処理開始から402ミリ秒後、「.then()」及び「.error()」メソッドによるonFullFilled関数／onRejected関数の登録が行われています。
その後、登録されたonFullFilled関数も実行され、これは処理開始から402ミリ秒後であることが分かります。

```
1. Create a Promise object. at 1589122444638                              // Promiseオブジェクト生成開始
2. At the top of the executor() of a Promise object. at 1589122444639     // executor実行開始
3. A Promise object has been created. at 1589122444639                    // Promiseオブジェクト生成完了
4. At the end of this program. at 1589122444639                           // コードの最終行に到達
5. At the function called delayed within the executor(). at 1589122444740 // executor内の処理がすべて完了。開始から102ミリ秒経過後
6. Registering the onFullFilled and onRejected callback functions has been started. at 1589122445040 // onFullFilled関数／onRejected関数の登録開始。開始から402ミリ秒経過後
7. The onFullFilled function has finished. {'message':'The executor has finished.','unixtime':1589122444741} at 1589122445040 // onFullFilled関数の実行完了。実行開始から402ミリ秒後に完了。executor内のresolve()を実行したのは開始から103ミリ秒経過後であることが併せてわかる。
```