忙しい人のための JavaScript Promise（ブラウザで今すぐ実験）

##とりあえず打ち込みましょう

F12キーを叩いてブラウザの開発者環境を開いて、コンソールに`var p = fetch('https://test-endpoint.azurewebsites.net/api/company?delay=10')`と打ち込みます。



![image.png](https://qiita-image-store.s3.amazonaws.com/0/103139/b0cf554d-2892-ca22-d2c2-b6c5fd478906.png)


ここでは、[ `fetch`関数](https://developer.mozilla.org/ja/docs/Web/API/Fetch_API/Using_Fetch)を使って指定したサーバーのエンドポイント（Azure Functionでテスト用として立てました）にリクエストを送信して、サーバーからのレスポンスを取ってくるようにしています。

実はこのエンドポイント、`delay`というパラメータを渡すことでサーバー側でわざと時間がかかるように細工をしています。`delay=10`の場合１０秒間スリープ状態にさせて、それからJSONのデータを含むレスポンスを返すようにしています。反応が悪いAPIをシミュレートしているわけです。

レスポンスが返ってくるまで最低でも１０秒かかるはずのコードなのに「一瞬」で変数`p`に戻り値が格納されました。開発環境コンソールが１０数秒固まってしまう、というようなことにもなりませんでした。なぜなら、変数`p`にはレスポンスではなく、「レスポンスの結果にいつでもアクセスできますよ」というPromise（約束）が代わりに格納されたからです。

##「１０秒間経ってしまう前」と「１０秒間経ってしまった後」のPromise

`fetch()`を呼び出してすぐ（１０秒以内）、そして１０秒以上待ったあとにもう一度、`p`の値をコンソールに表示してみましょう。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/103139/64cfdb56-68bb-1e14-0bf3-d77e25636b7f.png)

１回目と２回目の違いは・・・

- 「１０秒間経ってしまう前」pendingというステータスで、responseというプロパティはない
- 「１０秒間経ってしまった後」resolvedというステータスで、responseというプロパティがある

このようにPromiseは「ある仕事を任された請負人との約束」のように機能し、任された仕事の進捗状況と、さらに仕事が終わったならその結果へのアクセスを可能にする、という特徴があります。

##ここまでのおさらい

- `fetch関数`のように外部（サーバーなど）の応答を待つ関数は`Promise`を返すことが多い
- `呼び出し元は、そのような関数からPromise`をすぐに受け取り、ブロックされることはない
- `Promise`には状態があって、時間の経過とともに変化する
- `Promise`のスタータスが`pending`なら、仕事はまだ終わっていない
- `Promise`のステータスが`resolved`なら、仕事は終わっていて、結果へのアクセスが出来る

## Promise の結果を受け取る

Promiseから仕事の結果を受け取るコードを書いてみましょう。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/103139/2594812a-274b-fa4f-36db-b61100f5912e.png)


Promiseの`then()`という関数を呼び出しています。`then()`には、Promiseの結果（この場合は[response](https://developer.mozilla.org/ja/docs/Web/API/Response)）を受け取って処理するための関数を渡しています。

処理はresponseクラスの`json()`というメソッドを呼び出すことで、レスポンスからJSONペイロードを抽出するようにしています。その結果をさらに返して、`p2`という別の変数に格納しました。

ここで`p2`もPromiseであることに注意してください。これは[responseクラスの仕様](https://developer.mozilla.org/ja/docs/Web/API/Response)によると、`json()`メソッドもPromiseを返すとあります。

> Responseクラスのメソッドのうち、ストリームを読むメソッドは全部Promiseを返します。ストリームを読むという「仕事」も時間がかかる場合があるのでPromiseの仕組みを利用しているというわけです。この種の関数やメソッドはPromiseを多用していてよく見かけます。

では最後に`p2`に格納されているPromiseの結果を受け取りに行きましょう。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/103139/87660eba-acfc-637a-0e43-46279ccdf8ea.png)

ようやくAPIからのレスポンスに含まれていたJSONデータを取得できました。

##ここまでのコードのまとめ

下の3行のコードをそのままコンソールにコピペしてみましょう。

```javascript
var p = fetch('https://test-endpoint.azurewebsites.net/api/company?delay=10')
var p2 = p.then(function(response) { return response.json(); })
p2.then(function(json) { console.log(json); })
```

結果とどうなりましたか？１０秒ほどたってから突然、JSONのデータがコンソールに表示されたはずです。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/103139/e4ec4310-b4cb-56ba-797a-635f7741e2ec.png)


ここで重要なのは、このコードは２つのPromise（この場合`p`と`p2`）の状態を気にせずに書くことが出来ることです。それぞれのPromiseが一瞬で終わろうとも、数１０秒かかろうとも（エラー発生の場合は除く。エラー対策に関しては後述）同じコードです。

##コードをさらに整理する

`then()`関数はPromiseを返すので、変数`p`や`p2`を省略してひとつの制御文に整理することができます。

```javascript
fetch('https://test-endpoint.azurewebsites.net/api/company?delay=10')
.then(function(response) { return response.json(); })
.then(function(json) { console.log(json); })
```

結果は全く同じで、１０秒後にJSONデータが表示されました。ここでは、`fetch()`関数が返すPromiseの`then()`関数を呼び出し、その戻り値であるPromiseに対して、さらに`then()`関数を呼び出す、という風にチェーンにしています。

チェーンの順番が、実行の順番と同じなのでより自然に感じられます。

##エラーへの対応

ところで、例えばサーバーがデータを返さなかったり、JSONのデータが壊れていてパース出来ない、といった状況の場合はいったいどうしたらいいのでしょう？特に複数のPromiseをチェーンにしている時は、どこでエラーが起こるやもしれません。

ということで、エラーハンドラーを仕掛けましょう。`catch()`関数を最後に呼び出すことで、一種の例外処理を行います。

```javascript
fetch('https://test-endpoint.azurewebsites.net/api/company?delay=10')
.then(function(response) { return response.json(); })
.then(function(json) { console.log(json); })
.catch(function(err) { console.log("something went wrong: " + err.message); })
```

サーバーをいったんオフラインにして、実行してみるとこのような結果になりました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/103139/91329f3e-fba2-3b9a-0b6d-6af0bee90e2c.png)

これは`json()`メソッドが「パースするJSONが無いんですけど」と文句を言っている状況です。

> ちなみに、サーバーをOFFにしたので404が返ってきましたが、これは１つめのPromiseのステータスが`rejected`になったわけではありません。ちゃんと404のレスポンスという「仕事の結果」が得られたのでPromiseのステータスも`resolved`になっています。この例ではこの１つ目のPromiseの結果の処理において、エラーが発生した、ということです。

##おさらい、その２

- Promiseから仕事の結果を得るには`then()`関数を呼び出す
- `then()`関数には、結果を受け取る関数を定義して渡す
- Promiseの仕組みはネットワークへのアクセスやI/O関連のAPIで多用されている
- Promiseの仕組みを使うと「いつ仕事の結果が得られる状態になるか」を気にせずにコードを書ける。その時が来たら`then()`に渡した関数が実行を始める。それは0.1ミリ秒後かもしれないし、10秒後かもしれない。
- `then()`はPromiseを返すので、一連の処理をチェーンにして正しい順序を指定することが出来る
- チェーンにした一連の処理は書く順序と実行する順序が同じなので自然で読みやすい
- エラー対策として`catch()`関数を利用できる

##発展

この記事ではPromiseの利用に関して書きました。Promiseの作り方についてはまた別の機会で（もしくはここを拡張するかもしれません）。

ここまでの内容がわかったら[`Promise.all`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/all)の関数を見ると楽しいでしょう。


##おまけ

アロー関数式を使ってさらに整理するともっとスッキリしますね。Promiseとは関係ないですが。

```js
fetch('https://test-endpoint.azurewebsites.net/api/company?delay=10')
.then(response => { return response.json(); })
.then(json => { console.log(json); })
.catch(err => { console.log("something went wrong: " + err.message); })
```