Expressのエラー処理まとめ　【第3弾】非同期関数のエラーハンドラ

# はじめに
Expressを学び始めて、エラーハンドラの扱い方にいくつかのパターンがあるため、実装方法を整理。
本記事では、第3弾として非同期関数における、エラーハンドラの実装方法をまとめる。
1. <a href="https://qiita.com/mntocchi/items/2a87f9affdfea6ebae61">Expressのエラー処理まとめ　【第1弾】Expressのエラーハンミドラの定義</a>
2. <a href="https://qiita.com/mntocchi/items/62368e54377c72baf72e">Expressのエラー処理まとめ　【第2弾】バリデーションのミドルウェア</a>
3. Expressのエラー処理まとめ　【第3弾】非同期関数のエラーハンドラ（本記事）

## 1.非同期処理のエラーハンドラの実装方法
非同期関数内のエラーについてはExpressの標準のエラーでは取得することができない。非同期関数内のエラーをハンドリングするにはtry-catch構文で括り、ハンドリングする必要がある。

```javascript
// 非同期処理
const delayedError = (delay) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            const num = Math.floor(Math.random() * 10 + 1);
            return resolve(num);
        }, delay);
    });
};

// 非同期関数のエラーハンドリング
app.get('/number1', async (req, res, next) => {
    try {
        const num = await delayedError(1000);
            if (num > 5) { //6以上だったら正常
            res.send(`非同期処理成功:${num}`);
        } else { //5以下だったらエラー
            throw new AppError(`非同期処理失敗:${num}`, 401);
        }
    } catch (e) {
        next(e);
    }
});

```
処理の結果、非同期処理の関数内でエラーが発生した場合にエラーのハンドリングができていることが分かる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2830661/b0381a21-ff69-4992-b5e9-e2a5722cca54.png)

ちなみに、try-cachを外して実行してみると
````javascript
app.get('/number1', async (req, res, next) => {
    const num = await delayedError(1000);
    if (num > 5) {
        res.send(`非同期処理成功:${num}`);
    } else {
        throw new AppError(`非同期処理失敗:${num}`, 401);
    }
});
````
エラー発生時にハンドリングができていないことがわかる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2830661/e8fa6765-aae0-4fa4-a11b-78cc91cc4f67.png)

ということで、やはり非同期関数内のエラーハンドリングはtry-catch構文で拾ってあげる必要があることが分かった。

## 2.非同期関数内のエラーを取得するwrap関数を用意する
非同期関数をエラーハンドリングするために、try-catch構文で括る必要があることが分かったが、非同期関数は頻繁に記載するため、毎回try-catch構文で括るのは結構手間。そこで推奨されている方法が、非同期関数のエラーをハンドリングできる関数を１つ用意し(wrap関数という)、非同期関数を引数として渡してあげることで、その非同期関数のエラーをハンドリングする方法だ。まずは結論のコードから書いてみる。

````javascript
// wrap関数
function wrapAsync(fn) {
    return function (req, res, next) {
        fn(req, res, next).catch(e => next(e));
    }
}

// 非同期処理
app.get('/number1', wrapAsync(async (req, res) => {
    const num = await delayedError(1000);
    if (num > 5) {
        res.send(`非同期処理成功:${num}`);
    } else {
        throw new AppError(`非同期処理失敗:${num}`, 401);
    }
}));
````

処理結果も確認すると、確かにエラーのハンドリングができていることがわかる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2830661/3e5e384c-67e4-4d7c-4b4e-053572a171c9.png)

だけど、このwrap関数、コードの量が少ない割に、これがなぜ非同期関数をtry-catch構文で括った関数と同じになるのか理解することが難解。わかりそうで分かない...。そのため、次項から少しずつコードを変形しながら考えてみる。

## 3.非同期処理wrap関数の変形

### STEP1
初期状態（非同期関数がtry-catch構文で括られている状態。）
````javascript
app.get('/number1', async (req, res, next) => {
    try {
        const num = await delayedError(1000);
        if (num > 5) {
            res.send(`非同期処理成功:${num}`);
        } else {
            throw new AppError(`非同期処理失敗:${num}`, 401);
        }
    } catch (e) {
        next(e);
    }
});
````

### STEP2
非同期関数をごっそり引き抜き、ルートハンドラのコールバック関数を返す関数を定義する。
````javascript
function wrapAsync2() {
    return async (req, res, next) => {
        try {
            const num = await delayedError(1000);
            if (num > 5) {
                res.send(`非同期処理成功:${num}`);
            } else {
                throw new AppError(`非同期処理失敗:${num}`, 401);
            }
        } catch (e) {
            next(e);
        }
    }
}

app.get('/number2', wrapAsync2());
````

### STEP3
try-catch構文内で実行したい非同期関数をfnで定義して抜き出し、wrap関数の引数に渡す。ルートハンドラで得た（req,res,next）をfnの引数に渡すために、return functoion(req,res,net){}の関数内でfnを実行する。またfnは非同期処理でPromiseを返す関数のため、エラー発生時は「.catch」としてエラーをハンドリングすることができる。
````javascript
const fn = async function (req, res, next) {
    const num = await delayedError(1000);
    if (num > 5) {
        res.send(`非同期処理成功:${num}`);
    } else {
        throw new AppError(`非同期処理失敗:${num}`, 401);
    }
}

function wrapAsync3(fn) {
    return function (req, res, next) {
        fn(req, res, next).catch((e) => { //非同期処理の例外はPromiseの戻り値Rejetとしてchatcheへ
            next(e)
        })
    }
}

app.get('/number3', wrapAsync3(fn));
````

### STEP4
実行したい非同期関数fnをルートハンドラに入れる。
````javascript
function wrapAsync4(fn) {
    return function (req, res, next) {
        fn(req, res, next).catch((e) => {
            next(e)
        })
    }
}

app.get('/number4', wrapAsync4(async (req, res, next) => {
    const num = await delayedError(1000);
    if (num > 5) {
        res.send(`非同期処理成功:${num}`);
    } else {
        throw new AppError(`非同期処理失敗:${num}`, 401);
    }
}));
````

### STEP5
成形する。
Promiseの構文を簡略化し、ルートハンドラの関数ではnextを利用していないため削除。nextは暗黙的に保持しており、使用がなければ削除できる。
````javascript
function wrapAsync4(fn) {
    return function (req, res, next) {
        fn(req, res, next).catch(e => next(e));
    }
}

app.get('/number5', wrapAsync4(async (req, res) => {
    const num = await delayedError(1000);
    if (num > 5) {
        res.send(`非同期処理成功:${num}`);
    } else {
        throw new AppError(`非同期処理失敗:${num}`, 401);
    }
}));
````

# まとめ
非同期関数のエラーハンドラーはtry-catch構文を利用するすことで、実装できる。非同期関数を受取り、try-catch構文を1つ作ることで、他の非同期関数のエラーハンドリングに活用できる。DBを伴う処理などは非同期処理となるため、このようなエラーハンドリグを実装する必要があることが分かった。
エラー処理のまとめ全3弾終わり。