Express 미들웨어 체이닝에서 에러 핸들링 처리
문제 상황
인증 미들웨어에서 DB 조회 중 에러가 발생했는데, 클라이언트는 계속 대기하다가 타임아웃이 발생했다. 로그를 확인해보니 에러가 발생했지만 Express의 에러 핸들러로 전달되지 않았다.
app.use(async (req, res, next) => {
const user = await User.findById(req.session.userId);
req.user = user;
next();
});
미들웨어 내부의 Promise rejection이 catch되지 않아서 발생한 문제였다.
해결 방법
Express 4.x는 async/await을 네이티브로 지원하지 않는다. Promise가 reject되면 수동으로 next(error)를 호출해야 한다.
app.use(async (req, res, next) => {
try {
const user = await User.findById(req.session.userId);
req.user = user;
next();
} catch (error) {
next(error);
}
});
매번 try-catch를 작성하는 게 번거로워서 래퍼 함수를 만들었다.
const asyncHandler = fn => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
app.use(asyncHandler(async (req, res, next) => {
const user = await User.findById(req.session.userId);
req.user = user;
next();
}));
에러 핸들러 정리
에러 핸들러는 4개의 파라미터를 받아야 Express가 인식한다.
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(err.status || 500).json({
error: {
message: err.message
}
});
});
이후 프로젝트의 모든 async 미들웨어에 asyncHandler를 적용했고, 더 이상 타임아웃 문제는 발생하지 않았다. Express 5에서는 Promise rejection을 자동으로 처리한다고 하니, 정식 출시되면 이런 보일러플레이트가 줄어들 것 같다.