JavaScriptの非同期処理とコールバック、Promiseについてまとめてみた

# はじめに
JavaScriptの非同期処理とコールバック、Promiseについてあまり理解できていなかったので、自分なりに調べてまとめてみました。
間違いがあればコメント頂けると幸いです。

# 目次
- そもそも非同期処理とは
- 非同期処理のサンプル
- コールバック(Callback)とは
- コールバック(Callback)の問題点
- Promiseとは
- Promiseの書き方
- 参考

# そもそも非同期処理とは
まず、Promiseを知る上で、必要な概念として、非同期処理があります。
非同期処理とは、「ある処理が実行されてから終わるまで待たずに、次に控えている別の処理を行うこと」です。
ちなみに同期処理とは、「ある処理が実行されてから終わるまで、次に控えている処理は待つこと」です。
具体的に非同期処理が必要とされるものとしては、以下の3種類があります。
1. ネットワーク経由のリクエスト(例えばAjax呼び出し)
2. ファイルシステム関連の操作(ファイルの読み書きなの)
3. 意図的に遅延された操作(例えばアラーム)

上記を例えば同期的に処理すると、
1. 読み込みに10分かかるファイルの読み込みを行う。
2. 読み込みを行っている間、JavaScriptアプリケーションは他の処理を行えなくなります。

もし非同期的に処理すると、
1. 読み込みに10分かかるファイルの読み込みを行う。
2. 読み込みを行っている間、JavaScriptアプリケーションは他の処理を行うことができます。

JavaScriptのアプリケーションは「シングルスレッド」で実行されるので、1度にひとつのことを実行していきます。言い換えると、複数の処理を並列で実行することができません。ただ、最近のコンピューターはマルチコアが一般的であり、マルチタスキングが可能であるため、JavaScriptのアプリケーションでも非同期の処理を実現するために、非同期プログラミングの機能が追加されました。

# 非同期処理のサンプル
単純な例を見ていきます。組み込み関数setTimeoutは指定の時間だけ実行を遅延するものです。実際に実行してみるとコードの記述順とコードの実行順が異なることがわかります。

```javascript

console.log('setTimoutの前:' + new Date());
function f() {
    console.log('これは関数fの中:' + new Date());
}
setTimeout(f, 10000);/* 10秒後に関数fを実行。コールバック*/
console.log('setTimeoutの後')
console.log('これもsetTimeoutの後')

/* 実行結果
setTimoutの前:Sat Oct 10 2020 18:40:51 GMT+0900 (GMT+09:00)
setTimeoutの後
これもsetTimeoutの後
これは関数fの中:Sat Oct 10 2020 18:41:01 GMT+0900 (GMT+09:00
*/
```

# コールバック(Callback)とは
先ほどの`setTimeout(f,10000);`は、10秒後に関数fを呼び出す、というものでしたが、この10秒後に呼び出される関数fをコールバック関数といいます。つまり、***「将来のある時点で呼び出される関数」***をコールバック関数といいます。

コールバックを利用することで、次のことができるようになります。
1. 非同期処理の中で、決まった順序で処理を実行したい
2. 関数を渡す形にすることで、非同期処理を実行した後の処理の内容を自由に決めたい

# コールバック(Callback)の問題点
コールバックを利用することで、非同期の実行の管理ができますが、複数のコールバックを待つ必要があると管理が難しくなる問題点があります。これをコールバック地獄といいます。

```javascript

/* Nodeで実行 */
const fs = require('fs');

fs.readFile('a.txt', function (err, dataA) {
    if (err) console.error(err);
    fs.readFile('b.txt', function (err, dataB) {
        if (err) console.error(err);
        fs.readFile('c.txt', function (err, dataC) {
            if (err) console.error(err);
            fs.writeFile('d.txt', dataA + dataB + dataC, function (err) {
                if (err) console.error(err);
            });
        });
    });
});
```
これは3個のテキストファイルを読み込み、それらを1つのファイルに書き出すという処理ですが、深い入れ子のブロックができあがってしまいます。これでは保守性が悪いため、より安全で保守しやすいコードを記述するために、Promiseが考案されました。

# Promiseとは
Promiseとは、***非同期処理に対するオブジェクトとルールを仕様化することで、複雑な非同期処理をパターンに沿って記述、実行できるようになった仕組みのこと***をいいます。

先ほどの`3個のテキストファイルを読み込み、それらを1つのファイルに書き出すという処理`をPromiseに置き換えてみます。

```javascript

function readFile(file){
    return new Promise((resolve,reject)=>{
        fs.readFile(file,(err,data)=>{
            if(err) {reject(err); return;}
            resolve(data)
        })
    })
}

function writeFile(file, contents){
    return new Promise( (resolve,reject) =>{
        fs.writeFile(file, contents, (err) => {
            if(err) {reject(err); return;}
            resolve("書き込み成功");
        })
    })
}
let totalData;
readFile('a.txt')
    .then((data)=>{
        totalData = data;
        return readFile('b.txt')
    })
    .then((data)=>{
        totalData += data;
        return readFile('c.txt')
    })
    .then((data)=>{
        totalData += data;
        return writeFile('d.txt',totalData)
    })
```
先ほどよりコードが長くなってしまいましたが、Promiseにした方が見やすくはなったのではないでしょうか。

# Promiseの書き方
## 1. Promiseオブジェクトの作成
Promiseを利用するには、Promiseのオブジェクトを生成していきます。オブジェクトの生成は以下の通りです。
1. new Promise(fn) の返り値がpromiseオブジェクト
2. fn には非同期等の何らかの処理を書く

- 処理結果が正常なら、resolve(結果の値) を呼ぶ
- 処理結果がエラーなら、reject(Errorオブジェクト) を呼ぶ

つまり、下記のようなコードになります。

```javascript

new Promise((resolve, reject) => {
    /* 非同期の処理を記述 */
    setTimeout(() => {
        resolve('Hello World');
    }, 10000);
})

/* 上記をconsole.log()で確認してみた結果は、以下の通りで、promiseオブジェクトが返ってきている。
Promise { <pending> }
*/
```
## 2. Promiseオブジェクトへの処理を記述
先ほど作成したPromiseオブジェクトを返す関数を実装してみます。

```javascript

function asyncFunction() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('Hello World');
        }, 10000);
    })
}

asyncFunction()
/* asyncFunction()をconsole.log()で確認してみた結果は、以下の通り。
Promise { <pending> }
*/
```

ただし、このままですと、戻されるPromiseを何も利用できていないので、Promiseに用意されているthenメソッドを利用していきます。

```javascript

function asyncFunction() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('Hello World');
        }, 10000);
    })
}

asyncFunction().then(
  /* 非同期処理成功時に行う処理を記述 */
  function onFulfilled(value){
    console.log(value)
}).catch(
  /* 非同期処理失敗時に行う処理を記述 */
  function onRejected(err){
    console.log(err)
})

/* 実行結果
10秒後にHello Worldが出力される。
*/
```

これをアロー関数を使ってみると、以下のかたちで書けます。

```javascript

function asyncFunction() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('Hello World');
        }, 10000);
    })
}

asyncFunction().then((value) => {
    console.log(value)
}).catch((err) => {
    console.log(err)
})

/* 実行結果
10秒後にHello Worldが出力される。
*/
```

# 参考
- 初めてのJavaScript　ES2015以降の最新ウェブ開発
- [JavaScript Promiseの本](https://azu.github.io/promises-book/)