Node.js 비동기 에러 핸들링 삽질기
문제 상황
배포 후 간헐적으로 Node 프로세스가 조용히 종료되는 현상이 발생했다. 로그를 뒤져보니 Promise rejection이 제대로 처리되지 않은 것이 원인이었다.
app.get('/api/users/:id', (req, res) => {
getUserFromDB(req.params.id)
.then(user => res.json(user));
// catch가 없음!
});
임시 방편
일단 프로세스 레벨에서 unhandled rejection을 잡도록 했다.
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
// 로깅 후 프로세스 종료
process.exit(1);
});
근본적인 해결
모든 비동기 라우트 핸들러에 에러 처리를 추가했다. Express는 기본적으로 Promise rejection을 잡지 못하기 때문에 래퍼 함수를 만들었다.
const asyncHandler = fn => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
app.get('/api/users/:id', asyncHandler(async (req, res) => {
const user = await getUserFromDB(req.params.id);
res.json(user);
}));
그리고 전역 에러 핸들러를 추가했다.
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: 'Internal Server Error' });
});
교훈
Node 8에서 Promise를 쓸 때는 항상 rejection 처리를 명시적으로 해야 한다. async/await이 정착되면 좀 나아지겠지만, 현재로선 래퍼 패턴이 가장 실용적이다.