Expressのミドルウェアの挙動、特に非同期処理をおこなうにエラーが発生した場合の挙動が気になったので試してみた。 ミドルウェアで非同期処理をするのは前処理でデータベースに登録するケース等を想定している。
挙動を確認した実装はGitHubにおいた:nodejs-module-labo/express-middleware at main · s1r-J/nodejs-module-labo
実装で気をつけるポイント
先にポイントをまとめておく。
next()
は複数回呼び出しても問題ない- ミドルウェアでエラーを発生されるときは
next(err);
のように呼び出す- 呼び出さずにthrowするだけだとExpressがハングしてタイムアウトのエラーが発生する
- ミドルウェアで発生したエラーはエラーハンドリングでキャッチできる
- (エラーハンドリングでキャッチした場合など)レスポンスを複数回返すとエラーになるので
res.headersSent
で確認する
実装例と挙動
非同期処理が正常に実行される
ミドルウェアで非同期処理が正常に実行される場合の実装例(抜粋、全体はGitHubにある)は以下のとおり。
app.use('/no-async', (req, res, next) => { // 非同期的に前処理をするミドルウェア console.log('no-async middleware'); setTimeout(() => { console.log('no-async sleep'); next(); // ① }, 3000); next(); // ② }); app.get('/no-async', function (req, res) { // ③ console.log('Call no-async.'); res.send('Express response: no-async'); console.log('---'); });
挙動を解説する。
- 非同期なので
setTimeout
のコールバック処理は飛ばされて3000ミリ秒待つことなく、②のnext()
が実行される。 - ②の
next()
によってパスの処理(③)が実行され、サーバからレスポンスが返される。 - 3000ミリ秒後にコンソールに「no-async sleep」と表示される。
- ①の
next()
は実行されても再度③が呼ばれることはない。
- ①の
非同期処理でエラーが発生する
ミドルウェアでの非同期処理でエラーが発生する場合の実装例(抜粋、全体はGitHubにある)は以下のとおり。
app.use('/no-async-error', (req, res, next) => { // 非同期的に前処理をするときにエラーが発生するミドルウェア console.log('no-async-error middleware'); setTimeout(() => { try { if (true) { throw new Error('no-async-error'); } else { // エラーが発生しなかったらnextを呼ぶ next(); } } catch (err) { next(err); // ④ } }, 3000); next(); // ⑤ }); app.get('/no-async-error', function (req, res) { // ⑥ console.log('Call error.'); res.send('Express response: no-async-error'); console.log('---'); }); // ===エラーハンドリング=== app.use((err, req, res, next) => { // ⑦ console.log(`Error: ${err.name}`); if (res.headersSent) { // ⑧ // 非同期的に処理が実施されるとレスポンスが返却されている可能性がある console.log('response is already sent.') // レスポンスを複数回返すとエラーになる // res.status(500).send('Express response: error'); // ⑨ } else { res.status(500).send('Express response: error'); } console.log('---'); });
挙動を解説する。
- 非同期なので
setTimeout
のコールバック処理は飛ばされて3000ミリ秒待つことなく、⑤のnext()
が実行される。 - ⑤の
next()
によってパスの処理(⑥)が実行され、サーバからレスポンスが返される。 - 3000ミリ秒後に
setTimeout
のコールバックでエラーが発生し、④のnext(err)
が呼び出される。- このとき、
next(err)
が存在しないとExpressがハングしてサーバが停止する。 - 参考:Express でのエラー処理
- このとき、
- ④の
next(err)
は⑦のエラーハンドリングでキャッチされる。 - ⑧ではレスポンスが既に返却されているかを確認している。
- レスポンスを複数回返すとエラー(
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
)が発生する。(⑨のコメントアウトを戻すとエラーになる) res.headersSent
はレスポンスが返却済の場合にはtrue
、返却していない場合にはfalse
を返却するう- 参考:Express 4.x - API リファレンス
- レスポンスを複数回返すとエラー(
おわりに
非同期のときに気になる挙動については確認できた。
レスポンスを複数回返すとエラーになるので注意が必要だ。
今回エラーハンドリングにres.headersSent
を使った確認を実装した。パスのほうで時間がかかる処理があるならば、レスポンスを返却済でないのかを確認する処理を入れる、またはミドルウェアのエラーを握りつぶす(ログにだけは出しておく)ような実装をおこなう必要があるだろう。