非同期処理 Promise[JavaScript]


以前の記事で、JavaScripにおける非同期処理の考え方とコールバック関数についてお伝えしましたが、今回はpromiseについてまとめていこうと思います。

前回の記事：

https://qiita.com/shisshi_engineer/items/6cfbd29820ac8607ae5b

## 目次：

- [非同期処理の成功や失敗を表現するオブジェクト「コールバック関数の場合」](#非同期処理の成功や失敗を表現するオブジェクトコールバック関数の場合)
- [非同期処理の成功や失敗を表現するオブジェクト「Promise」](#非同期処理の成功や失敗を表現するオブジェクトpromise)

## 非同期処理の成功や失敗を表現するオブジェクト「コールバック関数の場合」

プロミス: 非同期処理の成功や失敗を表現するオブジェクトです。

例：
リクエストを投げて情報を取得する場合。APIなど

リクエストを投げる時には、時間がかかるかもしれないし、成功するか失敗するかわかりません。
・ログインしていないかもしれない
・URLが間違っているかもしれない
・インターネットがつながっていないかもしれない

など、色々なことが意図せず失敗している可能性があります。
このような時にpromiseを使用します。

また、promiseの処理は、コールバック関数でも記載できるので、
まずは、コールバック関数の場合の処理を書いてみます。


コールバック関数：

```js
//コールバック版

const fakeRequestCallback = (url, success, failure) => {
    const delay = Math.floor(Math.random() * 4500) + 500;
    setTimeout(() => {
        if(delay > 4000) {
            failure('コネクションタイムアウト')
        } else {
            success(`ダミーデータ(${url})`)
        }
    }, delay)
}
```

1. 関数fakeRequestCallbackを作成します。
2. この関数は、urlとsuccess(成功した場合のコールバック)とfailure(失敗した場合のコールバック) を受け取ります。 
3. 関数の内部：ランダムな数字を生成。
3-1. 500-5000までの数字がdelayに入ります。
3-2. delayがsetTimeoutに渡されています。
つまり、0.5秒〜5秒のsetTimeoutがランダムに発生するようになっています。
3-3. その中で、もし、4秒より多くかかったのであれば、失敗のコールバック関数を呼ぶようにしています。(failure)
3-4. それ以外の場合は成功のコールバック関数を呼ぶようにしています。(success)


関数を使ってみる：

```js
fakeRequestCallback('books.com',function() {
    console.log('成功！！');
},function() {
    console.log('エラー!!');
})
```

urlを適当にbooks.comとします。
成功した時用のコールバック関数と
```js
function() {
    console.log('成功！！');
```
失敗した時用のコールバック関数を定義しておきます。
```js
},function() {
    console.log('エラー!!');
})
```
これを実行してみると、
4秒よりも多くかかった場合はエラー!!
それ以外だと成功!!が出力されます。

また、コールバック関数を呼ぶときに、
渡している引数を使うこともできます。

```js
fakeRequestCallback('books.com',function(response) {
    console.log('成功！！');
    console.log(responce);
},function(err) {
    console.log('エラー!!');
    console.log(err);
})
```

```text:出力結果：
成功！！
ダミーデータ(books.com) //成功の場合

エラー!!
コネクションタイムアウト //失敗の場合
```
#### 1回目のリクエストをした後で、次のリクエストを投げたい場合

コールバック関数をネストして作成することができます。

```js
fakeRequestCallback('books.com/page1',function(response) {
    console.log('成功1！！');
    console.log(response);
    fakeRequestCallback('books.com/page2',function(response) {
        console.log('成功2！！');
        console.log(response);
        fakeRequestCallback('books.com/page2',function(response) {
            console.log('成功3！！');
            console.log(response);
        },function(err) {
            console.log('エラー3!!');
            console.log(err);
        })
    },function(err) {
        console.log('エラー2!!');
        console.log(err);
    })
},function(err) {
    console.log('エラー1!!');
    console.log(err);
})
```

1. 1回目のリクエストで成功するか失敗するかを確認します。
2. 1回目のリクエストで成功した場合、2回目のリクエストで成功するか失敗するかを確認します。
3. そして、2回目のリクエストでも成功した場合、3回目のリクエストで成功するか失敗するかを確認します。

上記のように、3回のリクエストの成否を行い、確認する場合は多々あります。

```text:出力結果：
成功1！！
ダミーデータ(books.com/page1)
成功2！！
ダミーデータ(books.com/page2)
成功3！！
ダミーデータ(books.com/page3)
```

上記のようなコールバック地獄になってしまっているコードを
より簡潔にみやすくしたものが
Promiseになります。


それでは、promiseに関する例を見てみたいと思います。

## 非同期処理の成功や失敗を表現するオブジェクト「Promise」

改めて、Promiseとは、JavaScripのオブジェクトです。

このオブジェクトは、未来のある時点で値を持つことを約束してくれるオブジェクト
何かは、必ず未来のある時点でもらうことができます。

ただし、（Promise）このオブジェクトを作った時には、まだどうなるかわかっていない
成功するかもしれないし、失敗するかもしれない

#### 先ほど作った上記の例をPromiseを使って実現してみる!

それでは、先ほど作ったコールバック関数(fakeRequestCallback)の例をPromiseで実現してみようと思います。

```js
//Promise版

const fakeRequestPromise = (url) => {
    return new Promise((resolve, reject) => {
        const delay = Math.floor(Math.random() *(4500)) + 500;
        setTimeout(() => {
            if(delay > 4000) {
                reject('コネクションタイムアウト')
            } else {
                resolve(`ダミーデータ(${url})`)
            }
        }, delay)
    })
}
```
Promiseには3つの状態があります。

・待機：初期状態。成功も失敗もしていない(pending)
・満足：処理が成功して完了したことを意味する（fulfilled）
・拒絶：処理が失敗したことを意味する（rejected）


Promiseはコールバック関数を渡す代わりに、
関数が返したオブジェクトに対してコールバック関数を「登録する」ようにする、というものです。

#### 成功の状態

まずは、fulfilled(満足)成功の状態をみてみます。

```js
const request = fakeRequestPromise('books.com/page');
request.then(() => {
    console.log('成功!!');
})
```

1. request(Promiseのオブジェクト)にfakeRequestPromiseを登録。
2. .thenメソッドを使い、これの最初の引数に、成功した時のコールバック関数を登録します。 

また、Promiseのオブジェクト（request）の中身を確認してみます。

fulfilled(満足)成功を表す状態になっていることも確認できます。


<img src = https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3679750/5a14c334-3ca3-5945-ea14-7149d79c8e66.png width = 60%>


#### 失敗の状態

続いて失敗の状態もみてみましょう。

失敗した時に走るコールバック関数を登録したい場合には、
.catchメソッドを活用します。

また、失敗した場合に、requestの中身を確認してみると、

rejected(拒絶)失敗を表す状態になっていることも確認できます。
```js
const request = fakeRequestPromise('books.com/page');
request.then(() => {
    console.log('成功!!');
}).catch(() => {
    console.log('失敗!!');
})
```

<img src = https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3679750/112efa94-7c08-05ca-28a6-765cfc63ddd2.png width = 60%>

#### 複数の成功と失敗の状態を確認する

fakeRequestCallbackの時のように、複数のリクエストを確認する場合はどのように書けば良いでしょうか？

```js
fakeRequestPromise('books.com/page1')
    .then(() => {
        console.log('成功1!!');
        fakeRequestPromise('books.com/page2')
            .then(() => {
                console.log('成功2!!');
                fakeRequestPromise('books.com/page3')
                    .then(() => {
                        console.log('成功3!!');
                    }).catch(() => {
                        console.log('失敗3!!');
                    });
                }).catch(() => {
                console.log('失敗2!!');
                });
        }).catch(() => {
            console.log('失敗1!!');
        });

```

上記のようにネストして記載することで、コールバック関数の例と同じように、
成功と失敗の状態を3回確認する関数を作成することができます。

しかし、これでは、どちらも複雑さは変わりません。

そこで、Promiseの書き方としてより簡潔に書ける書き方をまとめていきます。

#### Promiseの書き方の修正

上記の書き方では、階層が深くなり、コールバック関数出作成した例の時と複雑さが代わりないかと思います。
そこで、Promiseのreturnを使い、修正していこうと思います。

```js
fakeRequestPromise('books.com/page1')
    .then(() => {
        console.log('成功1!!');
        return fakeRequestPromise('books.com/page2')
    })
    .then(() => {
        console.log('成功2!!');
        return fakeRequestPromise('books.com/page3')
    })
    .then(() => {
        console.log('成功3!!');
    }).catch((err) => {
            console.log('失敗!!');
    });
```

Promiseは、thenからPromiseをreturnすると、次のthenに繋げていくことができます。
この性質を使うことで、階層を深くせず、使用することができます。

1. fakeRequestPromiseで作成した関数にreturnする形で2つ目の関数を設定します。
```js
fakeRequestPromise('books.com/page1')
    .then(() => {
        console.log('成功1!!');
        return fakeRequestPromise('books.com/page2')
    })
```
2. 2つ目の関数の処理は、.thenで繋げて、その下に書きます。(3つ目も同様です。)
```js
.then(() => {
        console.log('成功2!!');
        return fakeRequestPromise('books.com/page3')
    })
```

また、失敗の時の処理に関しては、
今回の場合、どこで失敗したとしても同じ処理を走らせることで問題ないので、
一番最後に.catchで繋げて失敗の時の関数を設定します。

```js
.catch(() => {
            console.log('失敗!!');
    });
```

上記のように、Promiseを使用することにより、圧倒的にすっきりと読みやすく、連続した非同期な処理を記載することができます。

#### Promiseが成功、あるいは失敗した時に渡されてくる値について

thenやcatchに渡しているコールバック関数には、パラメータを用意し、任意の値を取得することができる仕組みが、Promiseにはあります。

使用方法：

パラメータの第一引数を設定する。
今回は、thenの（）には、dataを、catchの（）にはerrとしています。

```js
const fakeRequestPromise = (url) => {
    return new Promise((resolve, reject) => {
        const delay = Math.floor(Math.random() *(4500)) + 500;
        setTimeout(() => {
            if(delay > 4000) {
                reject('コネクションタイムアウト')
            } else {
                resolve(`ダミーデータ(${url})`)
            }
        }, delay)
    })
}
```

```js
fakeRequestPromise('books.com/page1')
    .then((data) => {
        console.log('成功1!!');
        console.log(data);
        return fakeRequestPromise('books.com/page2')
    })
    .then((data) => {
        console.log('成功2!!');
        console.log(data);
        return fakeRequestPromise('books.com/page3')
    })
    .then((data) => {
        console.log('成功3!!');
        console.log(data);
    }).catch((err) => {
            console.log('失敗!!');
            console.log(err);
    });
```
それぞれ、console.logでdataとcatchの中身を確認してみると、
下記のようにdataの中身は、elseで設定したテンプレートリテラルの中身が出力されます。

errの場合も同様、rejectで設定していたコネクションタイムアウトが出力されています。

<img src = https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3679750/813d4a9f-11f3-3985-e7c9-e8f0f22c2da6.png width = 60%>