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로 별도 처리하도록 추가했다.