Expressのエラー処理まとめ　【第1弾】Expressのエラーハンミドラの定義

# はじめに
Expressを学び始めて、エラーハンドラの扱い方にいくつかのパターンがあるため、実装方法を整理。
本記事では、第1弾としてExpressにおける、基本的なエラーハンドラの使い方をまとめる。
1. Expressのエラー処理まとめ　【第1弾】Expressのエラーハンミドラの定義（本記事）
2. <a href="https://qiita.com/mntocchi/items/62368e54377c72baf72e">Expressのエラー処理まとめ　【第2弾】バリデーションのミドルウェア</a>
3. <a href="https://qiita.com/mntocchi/items/96107912784761b3bb0a">Expressのエラー処理まとめ　【第3弾】非同期関数のエラーハンドラ</a>
 
## 1.Expressエラーハンドラの定義方法
エラー処理ミドルウェア関数の定義方法は、ほかのミドルウェア関数と引数の数が異なる。4つ(err、req、res、next)の引数を持ち、第1引数にエラーのオブジェクトを受け取る。また、エラーハンドラは基本的に最後に記述する。

````javascript
// エラーが発生するルートハンドラ
app.get('/', (req, res, next) => {
    hoge.moge; //エラー発生
    res.send('エラーが発生しました。')
});

// エラーハンドラー
app.use((err, req, res, next) => {
    res.status(500).send(`エラーメッセージ：${err.message}`);
});
````
処理結果、関数hogeが見つからない、エラーをキャッチできていることが確認できる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2830661/42d498f5-bf36-04ee-db54-14a34eeeeab4.png)

## 2.エラーハンドラへの飛ばし方
処理の途中のミドルウェア内で何らかのエラーを判断して、エラーハンドラへ処理を渡したい場合は、next(err)　※errは何らかの値の引数　を記述することで、後続に続くルーティングとミドルウェア関数をスキップして、エラーハンドラーへ処理を移す。
```javascript
// エラーが発生するルートハンドラ
app.get('/error', (req, res, next) => {
    next('エラーが発生しました');
});

app.use((req, res) => {
    res.send('エラー時にスルーされるミドウェア')
})

// エラーハンドラー最後に定義
app.use((err, req, res, next) => {
    res.status(500).send(err);
});
````

処理結果、2つ目のミドルウェアがスキップされ、エラーハンドラへ処理が移る。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2830661/99980f6d-3f40-e7f7-a507-fde46ce91812.png)

## 3.自作エラークラスの作成
Express標準のエラークラスでは、エラーコードを指定してレスポンスする場合、明示的に「res.status(999※任意のエラーコード)」のように、記述する必要がある。レスポンスの都度、res.status(999)の記述するとエラーコードの指定漏れが発生しやすくなる。そのため、エラークラスを自作してエラーメッセージとエラーコードを合わせたエラーオブジェクトを生成するクラスを定義することで、エラコードの指定漏れ対策を行うことが推奨されている。

```javascript
//自作エラークラスの定義
class AppError extends Error { //標準のエラーハンドラーを継承
    constructor(message, status) {
        super(); //親クラスのコンストラクター呼び出し
        this.message = message; //引数で受け取ったメッセージをセット
        this.status = status; //引数で受け取ったエラーコードをセット
    }
};

// 権限エラーが発生するルートハドラ
app.get('/login', (req, res, next) => {
    //エラーオブジェクトを生成して、エラーハンドラへ処理を移す
    next(new AppError('アクセス権限がありません', 403)); 
});

//エラーハンドラー
app.use((err, req, res, next) => {
    const { status = 500, message = '何かエラーが起きました' } = err;
    res.status(status).send(message);
});
```
処理結果、エラー発生時に生成したエラーオブジェクトに保持するエラーコードがレスポンスされる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2830661/ae9fdaee-4836-e686-2cd4-1203c5a10fee.png)

基本的なエラーハンドラの定義方法はここまで、次は調べていて、なかなか解決できなかった疑問点を補足で記載。

## 4.「Next(err)」と「Throw new err」の違い
いろいろ調べていると、エラーを「Throw new err」で明示的に発生させてエラーハンドラへ飛ばすことができる、という内容を見つけた。ということで「3.自作エラークラスの作成」のコードを、「Next(new err)」から 「Throw new err」に書き換えて処理してエラーを発生させてみる。

```javascript
//自作エラークラスの定義
class AppError extends Error { //標準のエラーハンドラーを継承
    constructor(message, status) {
        super(); //親クラスのコンストラクター呼び出し
        this.message = message; //引数で受け取ったメッセージをセット
        this.status = status; //引数で受け取ったエラーコードをセット
    }
};

// 権限エラーが発生するルートハドラ
app.get('/login', (req, res, next) => {
    throw new AppError('アクセス権限がありません', 403); //nextからthrowに書き換えてみる
    // next(new AppError('アクセス権限がありません', 403));
});


//エラーハンドラー
app.use((err, req, res, next) => {
    const { status = 500, message = '何かエラーが起きました' } = err;
    res.status(status).send(message);
});

```
処理結果は、「エラーオブジェクト生成」→「エラーハンドラの処理」の流れで3.の時と同じ。
ここで疑問。はて、「next(err)」と「throw new err」の違いは何なのかと。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2830661/13107e89-407c-f11f-0607-00c16cfe51e0.png)

いろいろ調べてみると、2つの違いは以下の通り。
- Next(new err)：nextを呼び出した時点で、エラーオブジェクトを生成しエラーハンドラーへ処理が移る。エラーハンドラーに遷移する***即時性***があると言える。
- Throw new err：try-catchの中で使うことで、エラー処理がcatchの中に移る。（ただし、try-catchの構文外だと、エラーハンドラーへ処理が移る）エラーハンドラーへ移るまでに***段階性***があるといえる。

「Throw new err」について、try-catch構文をネストさせて、段階性を試してみる。

```javascript
// 権限エラーが発生するルートハドラ（tray-catch ネスト）
app.get('/login', (req, res, next) => {
    try {
        try {
            try {
                throw new AppError('アクセス権限がありません', 403);
            } catch (e) {
                e.message += '（追記1）エラーをキャッチしました。'
                throw new AppError(e.message, 403);
            }
        } catch (e) {
            e.message += '（追記2)エラーをキャッチしました。'
            throw new AppError(e.message, 403);
        }
    } catch (e) {
        e.message += '（追記3）エラーをキャッチしました。'
        next(e);
    }

//エラーハンドラー
app.use((err, req, res, next) => {
    const { status = 500, message = '何かエラーが起きました' } = err;
    res.status(status).send(message);
});
```

処理結果は、確かに段階的にエラー処理が実施されていることがわかる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2830661/a308740d-eb9c-c8fe-97eb-c919a4f03b39.png)

つまり処理の流れの結論としては以下の形。
- Next(err)：「エラー発生」→「エラーオブジェクトの生成」→「エラーハンドラーで処理」となり、***エラー直後にエラーハンドラーへ飛ばしたいときに有効***。
- Throw new err：「エラー発生」→「(N階層目try-cathの)エラーオブジェクトの生成」→・・・→「(1階層目try-cathの)エラーオブジェクトの生成」→「エラーハンドラーで処理」 ※try-catch構文で括られていない場合は、「next(err)」と同じ処理の流れ。***エラー発生時にその関数内で独自のエラー処理を行いたいときに有効***。

# まとめ
Expressのエラーハンドラーについて、基本的な動き方を整理し、一般的なエラークラスの定義の方法についても理解することができた。nextとthrowの動作の違いについては理解できたが、nextとthrowの動きが一緒だったり異なったりする理由については、Expressの内部まで調べないといけなそうなので、一旦ここまで。（Express全体をtry-catchの大枠と捉えるとtryのネストとの関係性がイメージできそうであるが）自身のレベルが上がってきたら立ち戻って調べてみようと思う。
エラー処理のまとめ第2弾へ続く。