Express.js エラー処理 まとめ

## 環境
Node.js 18.17.1
express 4.18.2

## 前提知識の共有
```js:pra1
const express = require('express');
const app = express();

const AccessLog = (req, res, next) => {
  console.log('処理１');
  console.log(Date.now(), req.method, req.originalUrl);
  next();
};

app.get('/', AccessLog, (req, res) => {
  console.log('処理２');
  res.send('Hello World');
});

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});
```

この時，`AccessLog`がミドルウェアになります．ミドルウェアの処理を行った後に，そのあとの処理を返すことになります．これにアクセスすると，`処理１`が実行された後に，`アクセスログ`が出力され，最後に`処理２`がコンソール画面に出力されることになります．

## 主な内容について
このようにNode.jsでは処理をミドルウェアに分割することが可能なのですが，その時のエラー処理をどのようにするかが自分の悩みでした．ここでは，後述の本を参考に理解したことを自分なりにまとめようと思います．

## 包括的なエラー処理
ここでは，包括的にエラーを処理する方法について記します．

- 同期関数でのエラー処理
- 非同期関数でのエラー処理
- 非同期ミドルウェアを呼び出した場合の挙動

### 同期関数でのエラー処理
```js:err1.js
const express = require('express');
const app = express();

app.get('/err', (req, res) => {
  throw new Error('Error!');
  console.log('Error!');
  res.send('Error!');
});

app.use((err, req, res, next) => {
  res.status(500).send('Something broke!');
});

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});
```

`err`エンドポイントにアクセスすることで，エラーを発生させるコードになります．
この時，次の場所が包括的なエラー処理を行っています．
```js
app.use((err, req, res, next) => {
  res.status(500).send('Something broke!');
});
```
<font color="red">この時，`res`以外の引数を利用していませんが，引数が4つあることを条件にアプリケーションがエラーを処理するため，たとえ利用しない場合であっても記述が必須になります．</font>

この時`localhost:3000`にアクセスすると，コンソールに`Error!`は表示されません．これは表示される前にエラーハンドラーに飛ばされてしまうためです．同様にレスポンスも`Error!`と表示されることはありません．

今回の場合は，`Something broke!`と表示されていると，再現が成功しています．

<strong>同期的なエンドポイントであれば，特に気にすることなく，エラーハンドラー内で処理を行うことでエラーをまとめて処理することが可能です.</strong>

### 非同期関数でのエラー処理
```js:err2.js
const express = require('express');
const app = express();

app.get('/err', async(req, res) => {
  throw new Error('Error!');
  console.log('Error!');
  res.send('Error!');
});

app.use((err, req, res, next) => {
  res.status(500).send('Something broke!');
});

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});
```
この時先ほどと同じようにアクセスすると，サーバーが落ちてしまいます．これは，非同期関数内でのエラーをエラーハンドラーでは拾うことができないためです．

これを解決するには，
<strong><font color="red">次のように`try catch`,`next()`を用いてエラーを明示的にエラーハンドラーに渡す必要があります．</font></strong>
実際のコードは次のようになります．
```js:err2_.js
const express = require('express');
const app = express();

app.get('/err', async (req, res, next) => {
  try {
    throw new Error('Error!');
    res.send('err');
  } catch (e) {
    next(e);
  }
});

app.use((err, req, res, next) => {
  res.status(500).send('Something broke!');
});

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});
```
このように記述することで，`async await`内のエラーもキャッチすることができるようになります．

### 非同期ミドルウェアを呼び出した場合の挙動
次に，非同期のミドルウェアを呼び出した場合の挙動です．
```js:err3.js
const express = require('express');
const app = express();

const ErrorMiddleware = async (req, res, next) => {
  await setTimeout(() => {
    next(new Error('Error!'));
  }, 1000);
};
app.get('/err', ErrorMiddleware, (req, res) => {
  res.send('err');
});

app.use((err, req, res, next) => {
  console.log(err);
  res.status(500).send('Something broke!');
});

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});
```
`err`エンドポイントにアクセスすることで，`ErrorMiddleware`が呼び出され，1秒後にエラーを吐くコードです．
このコードの場合は，サーバーが落ちずに`Something broke!`が表示されます．<br>
<strong>これはなぜでしょうか</strong>
```js
next(new Error('Error!'));
```
なぜかを説明します．
<strong>それは`next()`の中でエラーを発生させているためです．</strong>
つまりエラーをそのまま`エラーハンドラー`に渡しているから処理が落ちなかったのです．
この一文を次のように変更してみましょう．
```js
    throw new Error('Error!');
    next();
```
既に察している人も多いと思いますが，next()は呼び出されずに処理がエラーによって落ちてしまいました．
これの解決策としては先ほどのように`next()`内に書くことはもちろんのこと，<strong>非同期関数でのエラー処理</strong>の項目でやったように`try-catch`で囲むことが考えられます．

下に変更を記載しておきます．
```js:err3_.js
const express = require('express');
const app = express();

const ErrorMiddleware = async (req, res, next) => {
  try {
    throw new Error('Error!');
  } catch (e) {
    next(e);
  }
};
app.get('/err', ErrorMiddleware, (req, res) => {
  res.send('err');
});

app.use((err, req, res, next) => {
  console.log(err);
  res.status(500).send('Something broke!');
});

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});
```

このように記載することで，非同期のミドルウェアであってもエラーを包括的に処理することができます．

## まとめ
いかがだったでしょうか．expressのエラー処理について自分はとても悩んでいたので今回，後述の本を読むことで一歩深くexpressを利用しているときのエラー処理について理解が深まったように思います．

当たり前に必要なエラー処理というものですが，以外にもエラー処理についての記載がネット上に見つからなかったので自分なりにまとめてみました．

より実践的な方法などがある場合は教えていただけると幸いです。

## 参考図書
Node.jsについて，体系的にまとまっているのでNode.js中級者にとってちょうどよい本かもしれません．
自分は初めて触ったときに読んだのですが，少し難しく感じました...
- <strong><font color=green>実践Node.js入門</font></strong>

https://amzn.asia/d/fJPYnRP