Promise.all과 에러 핸들링 - 일부 실패해도 계속 진행하기

문제 상황

대시보드에서 여러 통계 데이터를 한 번에 가져오는 로직을 작성했다. 각 API가 독립적이라 Promise.all로 병렬 처리했는데, 하나의 API만 500 에러가 나도 전체가 실패했다.

const fetchDashboard = async () => {
  try {
    const [users, orders, revenue] = await Promise.all([
      fetchUsers(),
      fetchOrders(),
      fetchRevenue()
    ]);
    // orders API가 실패하면 users, revenue도 사용 못함
  } catch (error) {
    console.error('데이터 로딩 실패', error);
  }
};

주문 API가 일시적으로 불안정해도 사용자 수나 매출 통계는 보여줘야 하는데, 전체가 에러 처리되는 게 문제였다.

해결 방법

각 Promise를 catch로 감싸서 에러가 발생해도 reject되지 않도록 변경했다.

const fetchDashboard = async () => {
  const results = await Promise.all([
    fetchUsers().catch(err => ({ error: err })),
    fetchOrders().catch(err => ({ error: err })),
    fetchRevenue().catch(err => ({ error: err }))
  ]);

  const [users, orders, revenue] = results;

  // 각각 성공/실패 여부 확인
  if (!users.error) {
    renderUsers(users);
  }
  if (!orders.error) {
    renderOrders(orders);
  } else {
    showOrdersError();
  }
  if (!revenue.error) {
    renderRevenue(revenue);
  }
};

개선

반복되는 패턴을 함수로 분리했다.

const withErrorHandling = (promise) => {
  return promise
    .then(data => ({ data }))
    .catch(error => ({ error }));
};

const fetchDashboard = async () => {
  const results = await Promise.all([
    withErrorHandling(fetchUsers()),
    withErrorHandling(fetchOrders()),
    withErrorHandling(fetchRevenue())
  ]);

  results.forEach((result, index) => {
    if (result.data) {
      // 성공 처리
    } else {
      // 실패 로깅
      console.error(`API ${index} 실패:`, result.error);
    }
  });
};

결론

Promise.all은 하나라도 실패하면 전체가 reject되므로, 독립적인 비동기 작업들은 개별적으로 에러를 처리해야 했다. 이후 비슷한 상황에서는 이 패턴을 재사용하고 있다.

Promise.all과 에러 핸들링 - 일부 실패해도 계속 진행하기