Promise.all 병렬 처리 시 에러 핸들링 문제
문제 상황
대시보드에서 여러 통계 데이터를 동시에 불러오는 로직을 구현했다. 각 API는 독립적이고 하나가 실패해도 나머지는 표시되어야 했다.
const fetchDashboard = async () => {
const [users, orders, sales] = await Promise.all([
fetchUsers(),
fetchOrders(),
fetchSales()
]);
// ...
};
문제는 fetchOrders()가 실패하면 전체가 catch로 빠져서 정상적인 users, sales 데이터도 보여주지 못한다는 점이었다.
해결 방법 1: Promise.all + catch
각 Promise에 catch를 붙여서 reject를 방지했다.
const fetchDashboard = async () => {
const [users, orders, sales] = await Promise.all([
fetchUsers().catch(err => ({ error: err })),
fetchOrders().catch(err => ({ error: err })),
fetchSales().catch(err => ({ error: err }))
]);
if (!users.error) renderUsers(users);
if (!orders.error) renderOrders(orders);
if (!sales.error) renderSales(sales);
};
해결 방법 2: Promise.allSettled 폴리필
Promise.allSettled가 표준으로 제안되었지만 아직 브라우저 지원이 없어서 간단한 헬퍼를 만들었다.
const allSettled = promises => {
return Promise.all(
promises.map(p =>
p
.then(value => ({ status: 'fulfilled', value }))
.catch(reason => ({ status: 'rejected', reason }))
)
);
};
const results = await allSettled([
fetchUsers(),
fetchOrders(),
fetchSales()
]);
results.forEach(result => {
if (result.status === 'fulfilled') {
// 성공 처리
}
});
결론
당장은 방법 1로 구현했다. 코드가 더 직관적이고 팀원들이 이해하기 쉬웠다. allSettled는 제안 단계(Stage 3)라 프로덕션에 쓰기엔 이르다고 판단했다. 에러 로깅은 Sentry로 별도 처리하도록 추가했다.