ES6 Promise 체이닝과 에러 핸들링 정리
배경
레거시 프로젝트의 콜백 기반 API 호출 코드를 ES6 Promise로 전환하는 작업을 진행했다. Node 6에서 7로 업그레이드하면서 async/await도 고려했지만, 아직 프로덕션에 적용하기엔 이르다고 판단했다.
자주 한 실수
1. catch 후 체이닝
fetchUser()
.then(user => fetchPosts(user.id))
.catch(err => console.error(err))
.then(posts => renderPosts(posts)); // posts가 undefined
catch도 Promise를 반환하므로 이후 then이 계속 실행된다. 에러 발생 시 posts는 undefined가 되어 런타임 에러가 발생했다.
2. 중첩된 then
fetchUser().then(user => {
fetchPosts(user.id).then(posts => {
// 다시 콜백 지옥
});
});
Promise를 사용해도 중첩하면 의미가 없다. return을 명시적으로 작성해야 체이닝이 된다.
개선한 패턴
fetchUser()
.then(user => fetchPosts(user.id))
.then(posts => {
return posts.map(transformPost);
})
.then(transformedPosts => renderPosts(transformedPosts))
.catch(err => {
logger.error('Failed to load posts', err);
renderError();
});
마지막 catch 하나로 체인 전체의 에러를 처리할 수 있다. 각 단계에서 명시적으로 return하여 다음 then으로 값을 전달했다.
Promise.all 활용
여러 API를 병렬로 호출할 때 유용했다.
Promise.all([
fetchUser(userId),
fetchSettings(userId),
fetchNotifications(userId)
])
.then(([user, settings, notifications]) => {
renderDashboard({ user, settings, notifications });
})
.catch(err => {
// 하나라도 실패하면 여기로
handleError(err);
});
정리
Promise는 콜백보다 훨씬 읽기 좋은 코드를 만들어준다. 하지만 체이닝과 에러 처리 규칙을 정확히 이해하지 않으면 오히려 디버깅이 어려워진다. 팀 내 코드 리뷰에서 이 패턴들을 공유하고 컨벤션으로 정했다.