Promise.all과 Promise.race로 병렬 API 호출 최적화하기

문제 상황

대시보드 페이지에서 사용자 정보, 주문 내역, 알림 목록 등 5개의 API를 순차적으로 호출하고 있었다. 각 API가 평균 200~300ms 소요되다 보니 전체 로딩 시간이 1.5초를 넘어갔다.

function loadDashboard() {
  fetch('/api/user').then(user => {
    this.setState({ user });
    return fetch('/api/orders');
  }).then(orders => {
    this.setState({ orders });
    return fetch('/api/notifications');
  }).then(notifications => {
    this.setState({ notifications });
  });
}

해결 방법

API들이 서로 의존성이 없다는 점을 활용해 Promise.all로 병렬 처리했다.

function loadDashboard() {
  Promise.all([
    fetch('/api/user').then(r => r.json()),
    fetch('/api/orders').then(r => r.json()),
    fetch('/api/notifications').then(r => r.json()),
    fetch('/api/settings').then(r => r.json()),
    fetch('/api/stats').then(r => r.json())
  ]).then(([user, orders, notifications, settings, stats]) => {
    this.setState({ user, orders, notifications, settings, stats });
  }).catch(err => {
    console.error('Dashboard load failed:', err);
  });
}

로딩 시간이 300ms 정도로 줄어들었다. 가장 느린 API 하나의 응답 시간만큼만 기다리면 되기 때문이다.

Promise.race 활용

타임아웃 처리가 필요한 경우 Promise.race도 유용했다.

function fetchWithTimeout(url, timeout = 5000) {
  return Promise.race([
    fetch(url),
    new Promise((_, reject) => 
      setTimeout(() => reject(new Error('Timeout')), timeout)
    )
  ]);
}

하나의 API라도 실패하면 전체가 실패하는 Promise.all의 특성 때문에, 에러 핸들링을 각 fetch마다 해주는 것도 고려해야 한다.