Express 4로 API 서버 돌리고 있는데요, 라우트 핸들러를 async 함수로 쓰고 그 안에서 await 하는 DB 호출이 reject 되면 프로세스가 통째로 죽습니다. 에러 핸들링 미들웨어(인자 4개짜리)는 분명히 등록해놨는데 거기로 안 들어와요.
동기 코드에서 throw 하면 잘 잡히는데, async 안에서 throw 하거나 await한 promise가 reject되면 unhandledRejection 뜨면서 pm2가 재시작합니다.
app.get('/users/:id', async (req, res) => {
const u = await db.findUser(req.params.id) // 여기서 reject되면
res.json(u)
})
// 에러 미들웨어 (안 탐)
app.use((err, req, res, next) => {
res.status(500).json({ error: err.message })
})매번 try/catch로 감싸서 next(err) 부르면 되긴 하는데 라우트가 수십 개라 다 감싸는 게 너무 지저분합니다. 뭔가 깔끔한 방법이 없을까요?
댓글 2
해결은 결이 세 가지 있어요.
1) 제일 간단한 건 express-async-errors 패키지를 앱 진입점 맨 위에서 한 번 require 하는 겁니다. 라우터를 몽키패치해서 async 핸들러의 reject를 자동으로 next(err)로 넘겨줘요. 한 줄 추가로 끝나고 기존 라우트는 안 건드려도 됩니다.
2) 패키지 안 쓰고 싶으면 wrapper를 하나 만드세요. const wrap = fn => (req,res,next) => Promise.resolve(fn(req,res,next)).catch(next) 이런 걸 만들어서 app.get('/users/:id', wrap(async ...)) 식으로 감싸면 라우트마다 try/catch 안 써도 됩니다.
3) 근본은 Express 5로 올리는 거예요. Express 5부터는 라우터가 async 핸들러의 rejection을 자동으로 잡아서 에러 미들웨어로 보냅니다. 그래서 5에서는 위 코드가 그냥 동작해요.
당장은 1번 넣어서 급한 불 끄고, 여유 될 때 Express 5 마이그레이션을 검토하시는 걸 추천합니다. 그리고 어느 방법을 쓰든 process.on('unhandledRejection', ...)에 로깅 하나는 걸어두세요. wrapper가 놓친 게 있으면 거기서 드러납니다.