Promiseのメモ - その1: resolveとthen

いくらPromiseの解説を読んでもよく分からないので、自分で試しながら考えます。

## 実行のタイミング

Promiseオブジェクトを作って返す関数で実験します。解説記事の多くは、`new Promise`に渡す関数（executor）でsetTimeoutを使っていますが、ここでは使わないのがキモです。

```javascript
function makePromise(num) {
  return new Promise((resolve, reject) => {
    if(num % 2 == 0)
      resolve('OK');
    else
      reject('NG');
    console.log('resolve or reject called');
  });
}
```

`makePromise(2)`として`resolve('OK')`を試します。

```javascript
makePromise(2).then((x) => {
  console.log(`then callback: ${x}`);
}).catch((r) => {
  console.log(`catch callback: ${r}`);
});
console.log('then and catch called');
```


```text:結果
resolve or reject called
then and catch called
then callback: OK
```

executorは即座に実行されます。setTimeoutを使わなくてもthenに渡した関数は遅れて実行されることがわかります。

`makePromise(1)`として`reject('NG')`を試しても同様です。catchに渡した関数は遅れて実行されます。

```text:結果
resolve or reject called
then and catch called
catch callback: NG
```

executorは即座に実行され、thenやcatchに渡した関数は非同期で実行されます。executor内で呼び出すresolveとrejectは、thenやcatchに渡す関数とは別物ということです。ここがわかりにくい。

## resolveとthenは何をしているのか

resolveとthenのどちらを先に実行しても、thenに渡した関数は実行されます。上記の例は、resolveのほうが先に呼ばれています。次の例は、setTimeoutを使ってthenのあとでresolveを呼びます。

```javascript
function makePromise(num) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if(num % 2 == 0)
        resolve('OK');
      else
        reject('NG');
      console.log('resolve or reject called');
    }, 2000);
  });
}
```

```text:結果
then and catch called
resolve or reject called
then callback: OK
```

Promiseの仕組みは次のようになっていると思われます。なお、「履行」「棄却」はfulfilled / rejectedの訳です。

- executor内でresolveを呼び出すと、Promiseオブジェクトのステータスが「保留中」から「履行済み」となり、「履行済み結果の値」にresolveの引数（上の例では`"OK"`）がセットされる。**resolveはステータスを変える**もので、thenの関数を実行するわけではない。
- thenに関数を渡すと、履行済みのときに実行されるコールバック関数として登録される。**thenは関数を登録する**もので、実行するわけではない。
- コールバック関数を実行するのは、JavaScriptの実行環境。JavaScriptは定期的にPromiseオブジェクト一覧をチェックし、履行済みのものにコールバック関数が登録されていれば実行する。コールバック関数の引数は、resolveに渡した「履行済み結果の値」となる。

上記の説明は、resolveをreject、「履行済み」を「棄却済み」、thenをcatchとしてもたぶんだいたい同じ。

## そのほかに

executor内でresolveやrejectは何度でも呼べますが、効果があるのは最初の一回だけです。

```javascript
function makePromise(num) {
  return new Promise((resolve, reject) => {
    if(num % 2 == 0) {
      resolve('OK1');
      resolve('OK2');
    }
    else
      reject('NG');
    console.log('resolve or reject called');
  });
}
```

```text:結果
resolve or reject called
then and catch called
then callback: OK1
```

executor内でresolveもrejectも呼ばないと、Promiseオブジェクトはずっと保留中になります。

```javascript
function makePromise(num) {
  return new Promise((resolve, reject) => {
    console.log('do nothing');
  });
}
```

```text:結果
do nothing
then and catch called
```

続く。

- [Promiseのメモ - その2: チェーン、例外](https://qiita.com/kazutosato/items/94118b30ddf6c26cd24f) 
- [Promiseのメモ - その3: 小ネタ](https://qiita.com/kazutosato/items/b3afd079d45352e98574)
- [Promiseのメモ - その4: asyncとawait](https://qiita.com/kazutosato/items/c1c047991315128271f5)

## 参考文献
- [Promise - JavaScript | MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
- [Using Promises - JavaScript | MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises)
- [ECMAScript 2015 Language Specification – ECMA-262 6th Edition](https://www.ecma-international.org/ecma-262/6.0/)
- [JavaScript の Promise: 概要  |  Web Fundamentals  |  Google Developers](https://developers.google.com/web/fundamentals/primers/promises?hl=ja)
- [Node.jsでのイベントループの仕組みとタイマーについて - 技術探し](https://blog.hiroppy.me/entry/nodejs-event-loop)