fulfilled, rejected, settled...　Promiseの用語、全部分かりますか？

PromiseはES2015から導入されたJavaScriptの言語機能で、非同期処理を表すオブジェクトです。基本的な使い方としては、まず`fetch`などの非同期的な処理の結果としてPromiseオブジェクトが得られます。それに対して`then`メソッドなどを用いることで、非同期処理が終わった後の次の処理を登録できます。また、ES2017で追加されたasync/awaitの構文はPromiseを便利に扱うためのものです。これはJavaScriptに独特の概念ではなく、C#ではTask、RustではFutureなどと呼ばれているようです。

JavaScriptを使っている多くの方は、Promiseを多かれ少なかれ使ったことがあるでしょう。そして、Promiseには結果が2種類、すなわち成功と失敗があることをご存知かと思います。このように、**Promiseには状態があります**。成功した状態、失敗した状態の他に、処理待ちの状態（まだ成功か失敗か分からない状態）があるのも分かりますね。

この記事では、**Promiseの状態に関する用語を説明します**。具体的には、**pending**、**fulfilled**、**rejected**、**settled**、**resolved**を扱います。仕様書でいうと[25.6 Promise Objects](https://www.ecma-international.org/ecma-262/9.0/index.html#sec-promise-objects)の冒頭に書いてある内容です。

## 背景: `Promise.allSettled`

いきなり用語が英語になりましたが、別に英語力で皆さんを圧倒したいわけではありません。この記事の背景には、`Promise.allSettled`があります。これはECMAScriptのプロポーザルの1つで、2019年1月のTC39ミーティングでStage2に昇格しました。つまり、多分数年後くらいには`Promise.allSettled`がJavaScriptに追加される可能性が高いということです。

この`Promise.allSettled`は、`Promise.all`や`Promise.race`の仲間です。引数にPromiseの配列を受け取り、全てのPromiseの結果が出るまで待つという動作をします。`Promise.all`との違いは、渡されたPromiseのいずれかが失敗した場合に表れます。`Promise.all`はその場合即座に全体が失敗するのに対し、`Promise.allSettled`はその場合でも渡された全てのPromiseの結果が出るまで待ちます。詳細な動作は実装例を見てみると分かりやすいでしょう。`Promise.allSettled`はおおよそ次のように実装することができます（まだプロポーザルなので、今後仕様が変化する可能性があります）。

```js:Promise.allSettledの実装例
Promise.allSettled = promises =>
  Promise.all(
    promises.map(p =>
      p.then(
        result => ({ status: "fulfilled", value: result }),
        error => ({ status: "rejected", reason: error })
      )
    )
  );
```

この例を見て分かる通り、`Promise.allSettled`の結果は必ず成功となり、オブジェクトの配列を返します。各オブジェクトは渡されたそれぞれのPromiseの結果を表しており、成功の場合は`{ status: "fulfilled", value: 値 }`という形のオブジェクトが、失敗の場合は`{ status: "rejected", reason: エラーオブジェクト }`という形のオブジェクトが返ります。

実際に使ってみましょう。

```js:Promise.allSettledを使用する例
Promise.allSettled([
  fetch('/'),
  fetch('https://qiita.com/'),
  fetch('https://nonexistend.invalid/'),
]).then(console.log);
```

実行してみるとこんな感じになります（https://qiita.com/ で実行）。

[![Screenshot from Gyazo](https://gyazo.com/8b8b57164deb0a349ea6d9c6ba9e8c50/raw)](https://gyazo.com/8b8b57164deb0a349ea6d9c6ba9e8c50)

`Promise.allSettled`に3つのPromiseを渡した結果、配列が得られました。先ほど説明したように、それぞれのPromiseが成功したのか失敗したのかが`status`プロパティに入っている文字列で判別可能になっています。

ここでのポイントは、`status`プロパティに入っている`"fulfilled"`または`"rejected"`という文字列です。前者が成功したPromiseの結果、後者が失敗したPromiseの結果に対応します。この`Promise.allSettled`を使いたいときに、いちいちどちらがどちらなのか調べるのはなんとも馬鹿らしいですね。最低限、この2つの用語は覚えておきたいものです。

## 直感的な説明

では、本題である用語の説明に入ります。まず直感的な説明をします。

### fulfilled, rejected, settled

先ほど出てきた**fulfilled**と**rejected**は、既に結果が出ているPromiseの結果を表す用語です。**fulfilled**が成功した状態、**rejected**は失敗した状態です。

また、`Promise.allSettled`の名前に使われている**settled**というのは、fulfilledまたはrejectedである状態を指す言葉です。つまり、成功でも失敗でもいいからとにかく既に結果が出ているPromiseを指す言葉です。`Promise.allSettled`というのは、全てのPromiseが**settled**になるまで待つという意味だったのですね。

### pending

**pending**は、まだ結果が出ていない状態のことです。つまり、**settled**でないPromiseは**pending**です。

よく見ると、上の画像（`Promise.allSettled`の実行例）で`Promise.allSettled`の返り値に`Promise {<pending>}`と書いてありますね。これは`Promise.allSettled`が実行された瞬間はまだ結果が出ていないということを意味しています。

明らかに、全てのPromiseは**pending**、**fulfilled**、**rejected**のいずれかです。つまり、まだ結果が出ていないか、成功という結果が出ているか、失敗という結果が出ているかのいずれかということですね。

### resolved

しかし、まだひとつ解決していない用語が残っていますね。それが**resolved**です。以上の3つ（**settled**を含めると4つ）のほかに、全てのPromiseは**resolved**であるかそうでない（**unresolved**である）かに分類できます。

直感的には、**resolved**なPromiseというのは、既に自身の処理を終えているPromiseのことです。必然的に、**settled**なPromiseは**resolved**ですが、**pending**なPromiseは**resolved**な場合と**unresolved**な場合があります。

自分の処理が終わらないと結果が決まりませんから、**settled**なPromiseは**resolved**であるというのは分かりますね。問題は**pending**な場合です。ポイントは、Promiseというのは自身の結果を他のPromiseに委譲することができるという点です。AというPromiseがBというPromiseに結果を委譲したがBがまだ**pending**な場合、Aは**resolved**だが**pending**であることになります。コードで例を見てみましょう。

```js
const sleep = duration => new Promise((resolve, reject) => setTimeout(resolve, duration));

const p1 = new Promise((resolve, reject) => {
  resolve(sleep(3000).then(()=> console.log('hi')));
});
```

ここで定義した`sleep`は引数で指定された時間の後に解決されるPromiseを返す関数です。注目していただきたいのは`p1`です。ご存知のように、Promiseコンストラクタを用いてPromiseを作る場合、コンストラクタに関数をひとつ渡します。その関数は`resolve`と`reject`を受け取り、これを呼び出すことで作られたPromiseが解決（resolve）されます。`resolve`に値を渡した場合はその値がPromiseの結果（成功）となりますが、上の例のように`resolve`に別のPromise（今回は`sleep(3000).then(()=> console.log('hi'))`）を渡すことができます。これが委譲です。

つまり、`sleep(3000).then(()=> console.log('hi'))`が**fulfilled**になると同時に`p1`も**fulfilled**になります。今回の場合、`p1`が**fulfilled**になるのは3秒後ですね。`p1`は、作られると同時に**resolved**になります。なぜなら、即座に別のPromiseに結果を委譲しているからです。しかし、委譲されたPromiseは3秒後まで**pending**なので、`p1`も同様に3秒後まで**pending**です。3秒後に委譲先のPromiseが**fulfilled**になると同時に`p1`も**fulfilled**になります。

なお、`sleep(3000)`は3秒後まで**unresolved**です。なぜなら、`resolve`が呼ばれるまでは結果が決まらないし委譲も行なわれないからです。お分かりのように、PromiseコンストラクタでPromiseを作る場合、`resolve`または`reject`が呼ばれるまでが**unresolved**な状態で、呼ぶと**resolved**（まだ**pending**のままかもしれないし**settled**になるかもしれない）になります。

## 定義を見る

ここまで説明した用語は、冒頭でも触れた[ECMAScript仕様書](https://www.ecma-international.org/ecma-262/9.0/index.html#sec-promise-objects)に定義が載っています。

> Any Promise object is in one of three mutually exclusive states: fulfilled, rejected, and pending:
>
A promise `p` is fulfilled if `p.then(f, r)` will immediately enqueue a Job to call the function `f`.
A promise `p` is rejected if `p.then(f, r)` will immediately enqueue a Job to call the function `r`.
A promise is pending if it is neither fulfilled nor rejected.
A promise is said to be settled if it is not pending, i.e. if it is either fulfilled or rejected.
>
A promise is resolved if it is settled or if it has been “locked in” to match the state of another promise. Attempting to resolve or reject a resolved promise has no effect. A promise is unresolved if it is not resolved. An unresolved promise is always in the pending state. A resolved promise may be pending, fulfilled or rejected.

つまり、おおよそ以下の定義となっています。

- `p.then(f, r)`がすぐに`f`を呼び出すジョブを登録するならば`p`は**fulfilled**である。
- `p.then(f, r)`がすぐに`r`を呼び出すジョブを登録するならば`p`は**rejected**である。
- **fulfilled**でも**rejected**でもないPromiseは**pending**である。
- **pending**でないPromiseは**settled**である。
- **settled**なPromiseは**resolved**である。また、状態が別のPromiseと一致するように“固定”されているPromiseも**resolved**である。

特に**fulfilled**と**rejected**の定義が特徴的です。「*すぐに*`f`を呼び出すジョブを登録する」というのがまたややこしい概念ですが、「ジョブを登録」という言い方をしていることから想像がつくように、`f`は同期的には呼び出されません。このことは次の例を実行してみると分かります。

```js
Promise.resolve(3).then(console.log);
console.log('hi');
```

これを実行すると、

```
hi
3
```

という順番でログが表示されます。`Promise.resolve(3)`はすでに**fulfilled**なPromiseを返しますから、それに対して`then`を呼び出すことで、`console.log(3)`を呼び出すジョブがすぐに登録されます。しかし、そのジョブは同期的ではないため、次の文`console.log('hi')`よりは後になります。今の実行が終わり次第、ジョブとして登録された`console.log(3)`が実行されるのです。

これがどれくらい*すぐ*かというと、`setTimeout(fn, 0)`とかよりも*すぐ*です。

```js
/*
 * 3
 * hi
 * の順番で表示される
 */
Promise.resolve(3).then(console.log);
setTimeout(()=> console.log('hi'), 0);
```

非同期的に実行されたジョブの中からでも、*すぐ*にジョブを登録すれば待っているタイマーに割り込むことができます。*すぐ*というのはこれくらい*すぐ*です。

```js
/*
 * 3
 * 5
 * hi
 * の順番で表示される
 */
Promise.resolve(3).then(v=> {
  Promise.resolve(5).then(console.log);
  console.log(v);
});
setTimeout(()=> console.log('hi'), 0);
```

なお、仕様書的には一応、Promiseオブジェクトが**fulfilled**、**rejected**、**pending**のどの状態であるかという情報を[[PromiseState]]内部スロットに保持しています（[仕様書 25.6.6 Properties of Promise Instance](https://www.ecma-international.org/ecma-262/9.0/index.html#sec-properties-of-promise-instances)）。実際、[Promise.prototype.thenの定義](https://www.ecma-international.org/ecma-262/9.0/index.html#sec-properties-of-promise-instances)を見ると、確かに[[PromiseState]]が**fulfilled**や**rejected**の場合はすぐに当該関数を呼ぶという処理が書いてあります。

最後にクイズです。次のPromise `p2`, `p3`, `p4`が生成された直後の状態を分類してみてください。

```js
const p2 = new Promise(resolve => {
  resolve(Promise.resolve(123))
});
const p3 = new Promise(()=> {});
const p4 = Promise.resolve(p3);
```

答えは、`p2`が**fulfilled**、`p3`が**unresolved**、`p4`が**resolved**かつ**pending**です。

## まとめ

この記事ではPromiseの状態にまつわる用語である**fulfilled**、**rejected**、**pending**、**settled**、そして**resolved**について説明しました。`Promise.allSettled`の追加が現実味を帯びてきた今、これらの用語は今後身近になってくると思われます。

### Q&A

- **Q.** 何で`Promise.fulfill`じゃなくて`Promise.resolve`なの？
- **A.** Promiseオブジェクトを渡したら**fulfilled**じゃなくて**rejected**になる可能性があるからです（多分）。一方、`Promise.resolve`の返り値のPromiseは必ずすでに**resolved**です。
- **Q.** **settled**とかはいいとしても、長々と説明していた**resolved**とか何の役に立つの？
- **A.** （目を逸らす）

### 参考リンク
- [States and Fates](https://github.com/domenic/promises-unwrapping/blob/master/docs/states-and-fates.md)
- [Promise.allSettled プロポーザル](https://github.com/tc39/proposal-promise-allSettled)
- [Promise.allSettled @ TC39 January 2019](https://docs.google.com/presentation/d/1fWK9kMsvn2o66Lk6QUw3yITEfsE87yjZdTngoLTsUQ0/edit)