Promise.all의 에러 핸들링 문제와 해결

문제 상황

대시보드에서 여러 API를 동시에 호출해야 하는 요구사항이 생겼다. 사용자 정보, 통계 데이터, 알림 목록 등 5개의 엔드포인트를 병렬로 호출하기 위해 Promise.all을 사용했다.

const fetchDashboard = async () => {
  const [user, stats, notifications, activities, settings] = await Promise.all([
    api.getUser(),
    api.getStats(),
    api.getNotifications(),
    api.getActivities(),
    api.getSettings()
  ]);
  
  return { user, stats, notifications, activities, settings };
};

문제는 5개 중 하나라도 실패하면 전체가 실패한다는 점이었다. 통계 API가 일시적으로 타임아웃되면 사용자 정보조차 보여줄 수 없는 상황이 발생했다.

해결 방법

각 Promise를 래핑해서 reject되지 않도록 처리했다. 에러가 발생해도 null이나 기본값을 반환하게 만들었다.

const safePromise = (promise, fallback = null) => {
  return promise.catch(err => {
    console.error('API call failed:', err);
    return fallback;
  });
};

const fetchDashboard = async () => {
  const [user, stats, notifications, activities, settings] = await Promise.all([
    safePromise(api.getUser(), { name: 'Unknown' }),
    safePromise(api.getStats(), { total: 0 }),
    safePromise(api.getNotifications(), []),
    safePromise(api.getActivities(), []),
    safePromise(api.getSettings(), {})
  ]);
  
  return { user, stats, notifications, activities, settings };
};

고민 지점

모든 API 실패를 조용히 처리하는 게 맞는가에 대한 고민이 있었다. 결국 중요도에 따라 처리 방식을 달리했다.

  • 사용자 정보: 필수. 실패 시 전체 에러 처리
  • 통계/알림: 선택. 실패해도 빈 데이터로 UI 렌더링
const [user] = await Promise.all([
  api.getUser(), // 실패 시 reject
  safePromise(api.getStats(), { total: 0 }),
  safePromise(api.getNotifications(), [])
]);

단순해 보이는 병렬 처리지만 실제 프로덕션에서는 부분 실패를 고려해야 한다는 것을 배웠다.