【JavaScript】Promiseって何？

:::note warn
この記事では`async/await`を扱いません。
:::

# Promiseとは
[`Promise`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise)は**非同期処理を扱う**ためのクラスです。

非同期とは、実行に長時間かかるなどの理由で、**処理の実行完了を待たずに**次の処理ができるような仕組みのことです。
対義語は「同期」で、こちらは実行が完了するまで次にいけないような通常の処理を示します。

一部の関数やメソッドは`Promise`オブジェクトを返します。
JavaScriptに組み込まれているAPIでは、例えば`fetch`関数があります。

## 使い方
ここではHTTPリクエストを送る`fetch`関数を例に説明します。

`fetch`関数は、`Promise`を返さない関数と同じように呼び出すことができます。
しかし、戻り値はHTTPリクエストのレスポンスではなく、**`Promise`オブジェクト**です。

```js
const promise = fetch('https://example.com')
promise // Promise
```

`fetch`関数の呼び出しはさほど時間がかからずに終了します。
レスポンスが返ってくるにはそれなりの時間がかかるのにも関わらずです。

---

実は、`fetch`関数の呼び出しは**レスポンスが返るのを待たずに終了**します。

そして、レスポンスが返ってきたのかどうか、レスポンスの内容は、戻り値の`Promise`オブジェクトから確認できます。

### ステータス
`Promise`オブジェクトには、そのプロミスの現在の状態を示すステータスがあります。

ステータスには主に3つの種類があります。

- 待機 (`pending`): 初期状態、処理中
- 履行 (`fulfilled`): 処理が成功した
- 拒否 (`rejected`): 処理が失敗した

### `then`
`then`メソッドを使うと、プロミスが履行された=`fulfilled`になったときの処理を登録することができます。

`fetch`関数で使うなら、HTTPリクエストの**レスポンス**が返ってきて、その内容を使って処理したいときになります。
例えば、レスポンスヘッダの内容（`response.headers`）をコンソールに出力してみます。

```js
fetch('https://example.com')
  .then(response => console.log(response.headers))
  // responseにはResponseオブジェクトが入る

// 出力
Headers {
  "content-encoding": "gzip",
  "age": "233565",
  "cache-control": "max-age=604800",
  // 略
}
```

:::note warn
こちらのコードをブラウザのコンソールなどから実行した場合、CORSポリシーに違反しているため`Promise`が拒否（`rejected`）されてしまうことがあります。

その場合、Node.jsなどの環境から実行するか、https://example.com のコンソールから実行してみてください。
<!-- ヘッダーでどうにかなる？ -->
:::

### チェイン

また、`Response`オブジェクトからレスポンスボティを取り出すには、`text`メソッドを使います。

しかし、`text`は`Promise`を返します。
そのためひとつの`then`の中に`text`の結果を使う処理は書けません。

```js
fetch('https://example.com') // プロミスを返す
  .then(response => {
    const body = response.text() // textの戻り値はまた別のプロミス
    console.log(body) // Promiseオブジェクトが出力されてしまう
  })
```

そんなときに、`then`メソッドのチェインが使えます。
チェインというのは、`then`の呼び出しの後にさらに`then`を呼び出すことです。

```js
fetch('https://example.com')
  .then(response => response.text()) // 次のthenはこれが履行されるまで処理されない
  .then(body => console.log(body))

// レスポンスボディが出力される
```

なぜこれがができるのかと言うと、`then`メソッドが戻り値として**新しい`Promise`オブジェクト**を返すからです。

このコードでは4つのプロミスが作成されます。

1. `fetch`関数の戻り値のプロミス
    - 履行タイミング: レスポンスが返ってきたとき
    - 履行されたときの値: レスポンスを示す`Response`オブジェクト
2. 1の`Response`オブジェクトの`text`メソッドの戻り値
    - 履行タイミング: レスポンスボディの計算が完了したとき
    - 履行されたときの値: レスポンスボディを示す文字列
3. 1つ目の`then`
    - 履行タイミング: 1が履行され、2が履行されたとき
    - 履行されたときの値: 2と同じ
4. 2つ目の`then`
    - 履行タイミング: レスポンスボディのログ出力が終わったとき
    - 履行されたときの値: `undefined`（`console.log`の戻り値）

---

`Promise`の詳細はこちらの記事がわかりやすかったです。

https://qiita.com/cheez921/items/41b744e4e002b966391a

# `Promise`オブジェクトを作成する
`Promise`オブジェクトはコンストラクタを使うことで作ることができます。

コンストラクタは引数に関数を取ります。
この関数は、`Promise`オブジェクトが作られたときに**実行される処理**で、またどのタイミングでプロミスを履行 / 拒否するのかを制御します。

```js
new Promise((resolve, reject) => {
  // この処理はすぐに実行される

  // プロミスを履行したいときにresolveを呼ぶ
  resolve()

  // プロミスを拒否したいときにrejectを呼ぶ
  reject()
})
```

なお、`Promise`オブジェクトは基本的に**使い捨て**です。
同じオブジェクトを何度も使うのではなく、必要になったときに新しく作ります。

そのためAPIを作るときは、`Promise`オブジェクトのそのものではなく、**プロミスを返す関数**を定義することが多いです。

## 作例: `sleep`
ここでは、`setTimeout`を使いやすくラップした`sleep`関数を作ります。

:::note
[`setTimeout`](https://developer.mozilla.org/ja/docs/Web/API/Window/setTimeout)は、一定時間後に特定の処理を実行する関数です。
引数にコールバック関数と待つ時間を取ります。

```js
setTimeout(() => {
  console.log('Hello World!')
}, 1000) // 1000ms後に実行する

// 1000ms後
// Hello World!
```
:::

`sleep`関数は指定した時間処理を待機する関数です。
引数`ms`に待つ時間（ミリ秒）を指定し、**その時間が経ったら履行されるプロミス**を返します。

実装は割と単純です。
`setTimeout`を使って一定時間経ったら`resolve`を呼び出すだけです。

```js
function sleep(ms) {
  return new Promise(resolve => {
    setTimeout(resolve, ms)
  })
}
```

```js:呼び出し例
sleep(1000)
  .then(() => console.log('Hello World!'))

// 1000ms後
// Hello World!
```

また、この`sleep`関数は`async/await`と組み合わせることで真価を発揮します。

```js
await sleep(1000) // 1000ms秒待つ
console.log('Hello World!')

// 1000ms後
// Hello World!
```

詳細は[MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/async_function)をご覧ください。