JavaScript async/await 에러 처리 패턴 정리
문제 상황
레거시 API 호출 코드를 async/await으로 리팩토링하는 작업을 진행했다. Promise 체이닝보다 가독성은 좋아졌는데, 모든 함수마다 try-catch를 감싸는 것이 반복적이고 코드가 길어지는 문제가 있었다.
기본 try-catch 패턴
async function fetchUserData(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
return data;
} catch (error) {
console.error('Failed to fetch user:', error);
throw error;
}
}
매번 이렇게 작성하면 보일러플레이트가 많아진다.
Go 스타일 에러 처리
Go 언어의 [data, error] 패턴을 차용한 헬퍼 함수를 만들었다.
const to = (promise) => {
return promise
.then(data => [null, data])
.catch(err => [err, null]);
};
// 사용 예시
async function getUserProfile(userId) {
const [err, user] = await to(fetchUserData(userId));
if (err) {
return { error: 'User not found' };
}
return user;
}
명시적으로 에러를 체크하게 되어 누락 가능성이 줄어든다.
Higher-Order Function 패턴
Express 라우터에서 반복되는 try-catch를 제거하기 위해 래퍼 함수를 사용했다.
const asyncHandler = (fn) => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
// 사용
app.get('/users/:id', asyncHandler(async (req, res) => {
const user = await User.findById(req.params.id);
res.json(user);
}));
에러는 Express의 전역 에러 핸들러로 전달된다.
결론
상황에 따라 적절한 패턴을 선택하면 된다. 단순 API 호출은 Go 스타일, Express 라우터는 asyncHandler, 복잡한 비즈니스 로직은 명시적 try-catch를 사용하고 있다. 일관성이 가장 중요하다.