JavaScriptの非同期処理を制御する方法

#はじめに
今回はJavaScriptの非同期処理について解説していきます。

最初に非同期処理とはという解説をした後に、Promiseを使って制御する方法とasync/awaitを使って制御する方法を解説します。

頑張っていきましょう。

#非同期処理とは
そもそも非同期処理とは一体なんでしょうか。

以下のサイトに分かりやすい図があったので拝借しました。

[I-26-2. 非同期処理と同期処理の実装パターンと特徴](http://ossforum.jp/node/753#:~:text=%E5%90%8C%E6%9C%9F%E3%81%AE%E8%A8%AD%E8%A8%88-,%E9%9D%9E%E5%90%8C%E6%9C%9F%E5%87%A6%E7%90%86%E3%81%AF%E3%80%81%E3%81%82%E3%82%8B%E3%82%BF%E3%82%B9%E3%82%AF%E3%81%8C%E5%AE%9F%E8%A1%8C%E3%82%92%E3%81%97%E3%81%A6,%E3%81%95%E3%82%8C%E3%82%8B%E6%96%B9%E5%BC%8F%E3%81%A7%E3%81%82%E3%82%8B%E3%80%82&text=%2D%20%E9%9D%9E%E5%90%8C%E6%9C%9F%E5%87%A6%E7%90%86%E3%82%92%E5%85%A5%E3%82%8C%E3%80%81%E5%A4%9A%E9%87%8D,%E7%9B%AE%E6%8C%87%E3%81%99%E8%A8%AD%E8%A8%88%E3%81%8C%E8%A1%8C%E3%82%8F%E3%82%8C%E3%82%8B%E3%80%82)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411258/2a67db7f-a97a-4d1d-de3d-b053a5fdce8f.png)

非同期処理を端的に言うなら、`あるタスクが実行しているときに、他のタスクが別の処理を実行できる方式`ということができます。

プログラムを例にして、詳しく解説していきます。

普通、プログラムを実行すると、コードを上から順に一行ずつ実行していきます。

これは普段から当たり前のように使っている`同期処理`と呼ばれるものです。

しかし、サーバーと通信を行った時などは、この`同期処理`とは違った挙動をします。

具体的には、サーバーと通信を行う(fetchなど)行を実行したあと、その終了を待たずに次の行が実行されることになります。

例えば、fetchを使ってサーバーからデータを取ってきた後に、そのデータを出力するというプログラムを書いたとします。

その時、非同期処理によりfetchが完了する前に次のコードが実行されるため、データが出力できない(undefainedになる)です。

以下のコードで、非同期処理を制御せずにfetchメソッドを用いてgithubからユーザーIDを取得して、出力してみましょう。ちなみに、外部からメソッドをimportするためESModuleであることをpackage.json内に示しておきましょう。

```package.json
{"type": "module"}
```

```.js
import fetch from 'node-fetch';

const getGitUsername = () => {
    const url = 'https://api.github.com/users/hangi4343'

    fetch(url).then(res => res.json())
    .then(json => {
        console.log('非同期処理成功')
        return json.login
    }).catch(error => {
        console.error('非同期処理失敗', error)
        return null
    })
}

const message = 'GitのユーザIDは'
const username = getGitUsername()
console.log(message + username)
```

>GitのユーザIDはundefined
非同期処理成功

`const username = getGitUsername()`の行でgetGitUsername関数の完了を待たずに、その下の`console.log(message + username)`が実行されるためusernameがundefainedになってしまいます。

fetchの使い方については[こちら](https://javascript.keicode.com/newjs/fetch.php)の記事を参考にして下さい。

簡単に解説すると、fetchが返す`Response`オブジェクトを最初のthenで受け取り、その中で`res.json()`をreturnしています。その後、それをまたthen受け取り、メッセージを出力した後に`json.login`がreturnされます。また、この処理内でエラーが起きると、そのエラーをcatchで受け取り、その後の処理を実行します。

このコードが上手く実行されるために、`const username = getGitUsername()`の非同期処理を行う処理を、完了まで待ってから次のコードを実行することが考えられます。

この非同期処理の完了を待つために、Promiseやasync/awaitが用いられます。



#Promiseで非同期処理を制御
それでは、非同期処理を制御していきましょう。

以下の記事を参考にしました。

[Promiseについて0から勉強してみた](https://qiita.com/toshihirock/items/e49b66f8685a8510bd76)

[僕なりのPromise入門](https://qiita.com/valley/items/4cf782bd04170fddd5d5)

Promiseはresolveとrejectの２つの関数を引数に取ります。

* resolve : 処理が成功したときのメッセージを表示する関数
* reject : 処理が失敗したときのメッセージを表示する関数

```.js
import fetch from 'node-fetch';

const getGitUsername = () => {
    return new Promise((resolve, reject) => {
        const url = 'https://api.github.com/users/hangi4343'

        fetch(url).then(res => res.json())
            .then(json => {
                console.log('非同期処理成功')
                return resolve(json.login)
            }).catch(error => {
                console.error('非同期処理失敗', error)
                return reject("reject")
            })
    })
}

const message = 'GitのユーザIDは'
getGitUsername().then(username => {
    console.log(message + username)
})
```

>非同期処理成功
GitのユーザIDはhangi4343

非同期処理を制御することができました。

コードについて解説します。

Promiseは、resolveとrejectの２つの引数を持ちます。

getGitUsernameを実行すると、Promiseオブジェクトがreturnされます。

このPromiseオブジェクトに対して`then`を用いると、そのthenの引数にPromiseオブジェクトの`resolve`でreturnされた値が代入されます。

もう少し詳しく解説します。Promiseのコンストラクタの中でresolveが呼ばれると、Promiseはresolveの状態になります。thenはPromiseの状態がresolveになったときに呼ばれるので、結果的にthenの引数にresolveでreturnされた値が代入されることになります。

この場合では、`getGitUsername().then(username)=>`の部分で、returnで帰ってきたPromiseオブジェクトに対してthenを実行し、`resolve(json.login)`の`json.login`がthenの引数であるusernameに代入されます。

今回のコードでは示していませんが、`getGitUsername.then.catch(reject => console.log(reject))`のようにすると、Promiseのコンストラクタの中でrejectが呼ばれたときに、Promiseがrejectの状態になり、catch内の処理が実行されます(catchはPromiseがrejectの状態のときに呼ばれるため)。

Promiseはresoveとrejectとpendingの三つの状態を持ちます。returnしたPromiseオブジェクトに対してthenを実行すると、`Promiseオブジェクトがresolveの状態になるまで待ってから`then以降の処理が実行されます。

また、catchを実行すると`Promiseオブジェクトがrejectの状態になるまで待ってから`catch以降の処理が実行されます。

また、このthenやcatchの処理は非同期処理になるため、resolveやrejectが返される前にその次のコードが実行され、resolveやrejectが返ってきて初めて実行されます。

#async/awaitを使って非同期処理を制御
今度は、async/awaitを使って非同期処理を制御してみましょう。

こちらの記事を参考にしました。

[Promiseが分かれば簡単！async, await](https://qiita.com/valley/items/dd37deb77f06eb6c451c)

以下のコードです。

```.js
import fetch from 'node-fetch';

const getGitUsername = async () => {
    const message = 'GitのユーザーIDは';
    const url = 'https://api.github.com/users/hangi4343'

    const json = await fetch(url)
        .then(res => {

            console.log('非同期処理成功')
            return res.json()
        }).catch(error => {
            console.error('非同期処理失敗', error)
            return null
        })
    console.log(message + json.login)
}
getGitUsername()
console.log('end')
```

>end
非同期処理成功
GitのユーザーIDはhangi4343


それではコードの解説です。

asyncは、関数の前に書きます。このasyncにより、その関数の中でawaitが使えるようになります。

awaitは端的にいうと、`非同期処理を同期処理のように書けるようにしてくれるもの`という風に説明されます。

今回は`const json = await fetch(url)`の部分でawaitを使っています。awaitにより、`fetch(url)`の部分で値がreturnするまで待つことができます。

そのため、値が返ってきたあとに`console.log(message + json.login)`の部分のコードが実行されます。

しかし、awaitはあくまでPromiseを簡単に扱えるようにしたものであり、内部的にはawaitの右側の式を同期的に実行した後、resolveしたpromiseを生成し、awaitの左側および下のコード全てをthenの引数の関数として実行しています。

つまり、この場合においては`console.log(message + json.login)`の部分がPromiseにおけるthenの後に実行されるものであり、`console.log('end')`の部分はthenの後の処理ではありません。

つまり、`getGitUsername()`の実行の完了を待たずに、`console.log('end')`は実行されます。


#終わりに
今回の記事はここまでになります。

お疲れさまでした。