JavaScript の Promise / async / await をおさらいした

# はじめに

趣味のプログラミングで JavaScript を使い始めて随分になります。ところが、非同期処理や Promise になかなか慣れません。そろそろきちんと使えるようにしておきたいと思いました。

# 非同期関数を順に呼出する

例えば、3 つのファイルを順に読込して処理したいとします。以下のコードを書きます。

```javascript
import fs from 'fs';

fs.readFile("ファイル1", "utf-8", (err, data) => {
    console.log("readFile (1) finished.");
});

fs.readFile("ファイル2", "utf-8", (err, data) => {
    console.log("readFile (2) finished.");
});

fs.readFile("ファイル3", "utf-8", (err, data) => {
    console.log("readFile (3) finished.");
});
```

`fs.readFile()` は非同期関数であるため、「ファイル1」の読込が終わってから「ファイル2」続いて「ファイル3」の読込・・となりませんね。順に読込するにはどうしたらよかったでしょうか。

## コールバックで対応する

最初の呼出のコールバックで次の呼出して、そのコールバックで次の呼出して・・とします。

```javascript
fs.readFile("ファイル1", "utf-8", (err, data) => {
    console.log("readFile (1) finished.");

    fs.readFile("ファイル2", "utf-8", (err, data) => {
        console.log("readFile (2) finished.");

        fs.readFile("ファイル3", "utf-8", (err, data) => {
            console.log("readFile (3) finished.");
        });
    });
});
```

順に実行したい処理が増えると、コールバックのネストが深くなり、読みづらくなる欠点が指摘されていますね。

## async.js で対応する

サードパーティのライブラリ `async.js` （2012 年に公開）を使います。

[caolan/async: Async utilities for node and the browser](https://github.com/caolan/async)

```javascript
import async from 'async';

async.series([
    (done) => {
        fs.readFile("ファイル1", "utf-8", (err, data) => {
            console.log("readFile (1) finished.");
            done(err, null);
        });
    },
    (done) => {
        fs.readFile("ファイル2", "utf-8", (err, data) => {
            console.log("readFile (2) finished.");
            done(err, null);
        });
    },
    (done) => {
        fs.readFile("ファイル3", "utf-8", (err, data) => {
            console.log("readFile (3) finished.");
            done(err, null);
        });
    }
], (err, results) => {
    if (err) console.log(err);
});
```

コード量は増えてしまいましたが、順に実行するのが分かりやすく書けています。

## jQuery で対応する

サードパーティのライブラリ `jQuery` に `Deferred` が用意されました（2011 年）。

後述する `Promise` が用意されたため、今になって `Deferred` を使うことはないでしょう。

## Promise で対応する

JavaScript 言語に `Promise` が用意されました（2015 年）。これを使います。

```javascript
new Promise((resolve, reject) => {
    fs.readFile("ファイル1", "utf-8", (err, data) => {
        console.log("readFile (1) finished.");
        resolve();
    });
})
.then((resolve, reject) => {
    return new Promise((resolve, reject) => {
        fs.readFile("ファイル2", "utf-8", (err, data) => {
            console.log("readFile (2) finished.");
            resolve();
        });
    });
})
.then((resolve, reject) => {
    return new Promise((resolve, reject) => {
        fs.readFile("ファイル3", "utf-8", (err, data) => {
            console.log("readFile (3) finished.");
            resolve();
        });
    });
});
```

最初の呼出と以降の呼出のネストが違うのが気になりますね。↑
以下のようにしてもいいようです。↓

```javascript
Promise.resolve()
.then(() => {
    return new Promise((resolve, reject) => {
        fs.readFile("ファイル1", "utf-8", (err, data) => {
            console.log("readFile (1) finished.");
            resolve();
        });
    });
})
.then((resolve, reject) => {
    return new Promise((resolve, reject) => {
        fs.readFile("ファイル2", "utf-8", (err, data) => {
(以下略)
```

## 非同期関数を順に呼出できたけれど

非同期関数を順に呼出できるようになりました。ところが、注意しないといけない点があります。

```javascript
new Promise((resolve, reject) => {
    fs.readFile("ファイル1", "utf-8", (err, data) => {
        console.log("readFile (1) finished.");
        resolve();
    });
})
(中略)
.then((resolve, reject) => {
    return new Promise((resolve, reject) => {
        fs.readFile("ファイル3", "utf-8", (err, data) => {
            console.log("readFile (3) finished.");
            resolve();
        });
    });
});

console.log("all functions finished.")
```

実行すると

```
all functions finished.
readFile (1) finished.
readFile (2) finished.
readFile (3) finished.
```

呼出の後に書かれた処理が先に実行されます。↑
`new Promise() ～ then()` は、関数を呼出して、終了したときの処理を予約します。予約し終えると終了を待たずに、次の処理に移るわけです。

# 非同期関数を呼出する

関数を順に実行するだけなのに、随分と面倒なことをしないといけませんね。

同期関数なら、こんな面倒なことはありません。非同期関数は、なぜ欲しいのでしょう。

前述の `fs.reafFile()` は、同期処理できる `fs.readFileSync()` が用意されています。これを使うと

```javascript
const data = fs.readFileSync("ファイル", "utf-8");
console.log("readFileSync finished.");
```

これを入力画面から呼出すとします。（以下のコードはイメージです。）

```javascript
●●●●.addEventListner('○○○○', () => {
    const data = fs.readFileSync("ファイル", "utf-8");
    console.log("readFileSync finished.");
});
```

ファイルが大きくて読込に時間が掛かると、これを呼出した入力画面は、処理が終わるまでフリーズします。プログラムがハングアップしたように見えるでしょう。
これを解消するために、非同期で処理される関数を呼出するようにします。

```javascript
●●●●.addEventListner('○○○○', () => {
    fs.readFile("ファイル", "utf-8", (err, data) => {
        console.log("readFile finished.");
    });
});
```

呼出自体は直ぐに完了するので、呼出した側の入力画面の制御に戻ってきます。

# 非同期関数を作成する

`fs.readFile()` のように非同期処理できる関数を、作成できるでしょうか。

まず、同期処理する関数を作成して呼出してみます。

```javascript
function funcSync() {
    時間の掛かる処理
    const result = "result of funcAsync";
    return (result);
}

●●●●.addEventListner('○○○○', () => {
    const result = funcSync();
    console.log("function finished.", result);
});
```

## Promise を返す関数を作成する

JavaScript に用意（2015 年）された `Promise` を使います。

```javascript
function funcAsync() {
    return new Promise((resolve, reject) => {
        時間の掛かる処理
        const result = "result of funcAsync";
        resolve(result);
    });
}

●●●●.addEventListner('○○○○', () => {
    funcAsync()
    .then((result) => {
        console.log("function finished.", result);
    });
});
```

## async を使って関数を作成する

JavaScript に用意（2017 年）された `async` を使います。

```javascript
async function funcAsync() {
    時間の掛かる処理
    const result = "result of funcAsync";
    return (result);
}

(以下略)
```

`return new Promise() ～ resolve()` を `async` で書換しています。呼出する側は同じです。

## await を使って関数を呼出する

`async` と併せて用意された `await` を使います。

```javascript
(前略)

●●●●.addEventListner('○○○○', async () => {
    const result = await funcAsync();
    console.log("function finished.", result);
});
```

`関数呼出.then()` を `async ～ await` で書換しています。呼出される側は同じです。

## await を使うとよかったこと

`async ～ await` を使うことで、コード量も減って見やすくなります。
関数の定義に `async` をつけるので、非同期で実行されることが分かります。

呼出する側は `関数呼出.then()` でもいいのですが、`await` を使うといいことがありました。IDE などでブレークポイントを設定してステップ実行したときです。

```javascript
    funcAsync()                                   ⇐ここにブレークポイントを設定
    .then((result) => {
        console.log("function finished.", result);
    });

    次の処理                                       ⇐ステップ実行するとここに来る
```

```javascript
    const result = await funcAsync();             ⇐ここにブレークポイントを設定
    console.log("function finished.", result);    ⇐ステップ実行するとここに来る

    次の処理
```

呼出して終了までステップ実行したいとき、`then()` の内にブレークポイントを設定しないといけなかったのが、必要なくなりました。

# 呼出された関数で問題あったとき

呼出された関数で処理していて問題あったとき、それを呼出した側に返すのはどうしたらいいでしょうか。

## Promise で reject() する

そのために `Promise` に `reject()` が用意されています。

```javascript
function funcResolve() {
    return new Promise((resolve, reject) => {
        var result = "result funcResolve";
        resolve(result);    // 処理に問題なし
    });
}

function funcReject() {
    return new Promise((resolve, reject) => {
        var result = "result funcReject";
        reject("error funcReject");    // 処理に問題あり
    });
}
```

```javascript
    funcResolve()
    .then((result) => {
        console.log("function finished.", result);
    });

    funcReject()
    .then((result) => {
        console.log("function finished.", result);
    });
```

これを実行すると

```
function finished. result funcResolve
Uncaught UnhandledPromiseRejection UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "result funcReject".
```

`resolve()` が実行されたときは `then()` で、`reject()` が実行されたときは `catch()` で受取するようになっています。

```javascript
    (前略)
    
    funcReject()
    .then((result) => {
        console.log("function succeeded.", result);
    })
    .catch((error) => {
        console.log("function failed.", error)
    });
```

実行すると

```
function failed. error funcReject
```

## async 関数で throw する

上記の処理を `async` で書くときはどうなるでしょうか。

```javascript
async function funcResolve() {
    const result = "result funcResolve";
    return result;
}

async function funcReject() {
    const result = "result funcReject";
    throw new Error("error funcReject");    // 処理に問題あり
}

(以下略)
```

呼出される関数で `throw` するようです。このときは `Error` オブジェクトを生成して返すのが定番のようです。↑

```javascript
    (前略)
    
    funcReject()
    .then((result) => {
        console.log("function succeeded.", result);
    })
    .catch((err) => {
        console.log("function failed.", err.message)
    });
```

実行すると

```
function failed. error funcReject
```

呼出する側で `catch()` で受取できています。`Error` オブジェクトを `throw` しているので、それを受取しています。

## try ～ catch で受取する

`関数呼出.then()` でなく `await` を使うとどうなるでしょうか。

```javascript
(前略)

    let result = await funcResolve();
    console.log("function finished.", result);

    result = await funcReject();
    console.log("function finished.", result);
```

これを実行すると

```
function finished. result funcResolve
Uncaught Error Error: error funcReject
```

`throw` したときは `try ～ catch` しないといけませんね。

```javascript
    (前略)

    try {
        let result = await funcReject();
        console.log("function succeeded.", result);
    }
    catch(err) {
        console.log("function failed.", err.message);
    }
```

これを実行すると

```
function failed. error funcReject
```

調べていくと、`await ～ catch` の書き方もあるようです。

```javascript
    (前略)

    let result = await funcReject()
    .catch((err) => {
        console.log("function failed.", err.message);
        return "function failed";
    });
    console.log("function finished.", result);
```

実行すると

```
function failed. error funcReject
function finished. function failed
```

ところで、`throw` して `then() ～ catch()` で受取できましたが、`reject()` して `try ～ catch` できるでしょうか。

```javascript
function funcReject() {
    return new Promise((resolve, reject) => {
        var result = "result funcReject";
        reject("error funcReject");    // 処理に問題あり
    });
}
```
```javascript
    (前略)

    try {
        let result = await funcReject();
        console.log("function succeeded.", result);
    }
    catch(error) {
        console.log("function failed.", error);
    }
```

実行すると

```
function failed. error funcReject
```

呼出される関数が `Promise` で実装されているか `async` で実装されているか気にしないで、呼出する側も `then() ～ catch()` でも `try ～ catch` でも実装できますね。