Expressのミドルウェアの挙動、特に非同期処理をおこなうにエラーが発生した場合の挙動が気になったので試してみた。
ミドルウェアで非同期処理をするのは前処理でデータベースに登録するケース等を想定している。
挙動を確認した実装はGitHubにおいた:nodejs-module-labo/express-middleware at main · s1r-J/nodejs-module-labo
先にポイントをまとめておく。
next()
は複数回呼び出しても問題ないnext(err);
のように呼び出す
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()
によってパスの処理(③)が実行され、サーバからレスポンスが返される。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()
によってパスの処理(⑥)が実行され、サーバからレスポンスが返される。setTimeout
のコールバックでエラーが発生し、④のnext(err)
が呼び出される。
next(err)
が存在しないとExpressがハングしてサーバが停止する。next(err)
は⑦のエラーハンドリングでキャッチされる。Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
)が発生する。(⑨のコメントアウトを戻すとエラーになる)res.headersSent
はレスポンスが返却済の場合にはtrue
、返却していない場合にはfalse
を返却するう非同期のときに気になる挙動については確認できた。
レスポンスを複数回返すとエラーになるので注意が必要だ。
今回エラーハンドリングにres.headersSent
を使った確認を実装した。パスのほうで時間がかかる処理があるならば、レスポンスを返却済でないのかを確認する処理を入れる、またはミドルウェアのエラーを握りつぶす(ログにだけは出しておく)ような実装をおこなう必要があるだろう。
Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。
また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!
こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?
コメント