【JavaScript】Promiseをしっかり理解したい【その2】

## 初めに
前回に引き続きPromiseの学習を行います。
[【JavaScript】Promiseをしっかり理解したい【その1】](https://qiita.com/kum44/items/d3eba06e3d942fbdc049)

## Promiseの書き方

前回も軽く触れましたがPromiseを使った非同期処理の書き方について見ていきましょう。

```js:Promiseによる非同期処理の書き方
function asyncFunc(data) { // ①非同期処理を関数としてまとめる
  return new Promise((resolve, reject) => { // ②Promiseをインスタンス化し戻り値に設定
    setTimeout(() => {
      console.log('--処理実行--');
      if (data) {
        resolve(data * 2); // ③処理の成功時にresolveを呼び出す
      } else {
        reject('未入力です'); // ④処理の失敗時にrejectを呼び出す
      }
    }, 3000);
  });
}

asyncFunc(2) // ⑤非同期関数の呼び出し
   .then((result) => { // ⑥Promiseオブジェクトでresolveされた場合thenメソッドへ処理が移る
     console.log(result);
   })
   .catch((error) => { // ⑦Promiseオブジェクトでrejectされた場合catchメソッドへ処理が移る
     console.log(error);
   });
```
```console:実行結果
--処理実行--
4
```

もちろん他にも書き方はありますが、上記の例が個人的に簡潔でわかりやすいかなと思います。中身を見ていきましょう。①非同期処理を`asyncFunc`のように関数としてまとめます。②その関数では、Promiseオブジェクトが戻り値として返されるように記述します。⑤まとめた関数を呼び出し、③Promise内でresolveされた場合、⑥Promiseのインスタンスメソッドである`then`に処理が移ります。`then`メソッドでは主に非同期処理が完了した後に実行したい処理を記述します。また、④Promise内でrejectされた場合、⑦同じくPromiseのインスタンスメソッドである`catch`に処理が移ります。rejectしてあげることでエラーハンドリングをすることができます。

### Promiseを使用しなかった場合

もし、上記の例でPromiseを使用しなかった場合はどうなるでしょうか。

```js:Promiseを使用しなかった場合
function asyncFunc(data) {
  setTimeout(() => {
    console.log('--処理実行--');
    if (data) {
      return data * 2;
    } else {
      return '未入力です';
    }
  }, 3000);
}

const result = asyncFunc(2);
console.log(result);
```
```console:実行結果
undefined
--処理実行--
```
Promiseを使用しなかった場合、setTimeout関数内の処理が始まる前に最後の行のconsole.logが呼び出されてしまっていることがわかります。

## thenメソッド

`then`はPromiseのインスタンスメソッドです。

```js:thenの構文
const p = new Promise(...);
p.then(onFulfilled, onRejected);
```

#### onFulfilled
Promiseオブジェクト内でresolveした時、`onFulfilled`が呼ばれます。
#### onRejected
Promiseオブジェクト内でrejectした時、`onRejected`が呼ばれます。

`onFulfilled`と`onRejected`はどちらもオプショナル（任意）な引数なので、設定しなくても構いません。
thenメソッドで`onRejected`が扱えるということは、thenメソッドだけで成功時と失敗時の両方の処理が書けるということになります。
今までの例でエラー処理はcatchメソッドを使用していましたが以下のようにthenメソッドで一纏めにすることも可能です。

```js:thenメソッド内でエラー処理も行う場合
function asyncFunc(data) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('--処理実行--');
      if (data) {
        resolve(data * 2);
      } else {
        reject('未入力です');
      }
    }, 3000);
  });
}

asyncFunc() // 引数なし
   .then(
      (result) => { // ①onFulfilled
        console.log(result);
      }, 
      (error) => { // ②onRejected
        console.log(error);
      }
   );
```
```console:実行結果
--処理実行--
未入力です
```
上記の例では、resolveされた場合は①の`onFulfilled`が呼ばれ、rejectされた場合は②の`onRejected`が呼ばれます。asyncFunc関数の呼び出し時に引数を渡していないので、Promiseオブジェクト内でrejectされ、実行結果からも`onRejected`が呼び出されていることがわかります。

## catchメソッド
`catch`もthenと同様にPromiseのインスタンスメソッドです。

```js:catchの構文
const p = new Promise(...);
p.catch(onRejected);
```

catchメソッドの場合は`onRejected`しかありません。
今までの例でもエラー処理はcatchメソッドを使用してきました。個人的にthenメソッドで`onFulfilled`と`onRejected`の両方を書くよりも、catchメソッドを使って処理を別々に分けたほうがわかりやすいかと思っています。

## Promiseの状態
Promiseオブジェクトには3つの`状態`というものがあります。

#### Fulfilled
Promiseオブジェクト内でresolve(成功)した時。このとき`onFulfilled`が呼ばれます。

#### Rejected
Promiseオブジェクト内でreject(失敗)した時。このとき`onRejected`が呼ばれます。

#### Pending
`Fulfilled`または`Rejected`ではない時。Promiseオブジェクトが作成された初期状態や処理の実行中がこの状態に当てはまります。

Promiseオブジェクトの状態は、一度`Pending`から`Fulfilled`や`Rejected`に移ると、そのPromiseオブジェクトの状態はそれ以降変化しません。なので、then等に書いた処理が実行されるのは一度きりになります。
また、このことから`Fulfilled`と`Rejected`のどちらかの状態であることを、「確立した、不変な」を意味する`Settled`と表現することがあるそうです。

#### Settled
Promiseオブジェクト内でresolve(成功) または reject(失敗) した時。つまり`Fulfilled`か`Rejected`の状態である時。

Promiseを抽象化すると`Pending(未決)`から`Settled(解決)`の状態変化である、と言えるでしょう。Promiseの状態については実装等で直接使用することはありませんが、Promiseの理解の為に頭の片隅に入れておくと良いかと思われます。

## まとめ
実際に実装する分には、今回のPromiseの書き方だけ押さえておけば問題ないかと思います。ただ、根本的な理解だとまだまだ足りないことが多いと感じているので引き続き学習していきたいと思います。

## 次回
[【JavaScript】Promiseをしっかり理解したい【その3】](https://qiita.com/kum44/items/cba22109698ca76a9dce)