자바스크립트 Promise 조합 정리: 병렬 처리, 타임아웃, 부분 성공 처리까지

개발/javascript&typescript1 2025. 12. 29. 08:03

1) finally: "성공/실패와 무관하게" 마지막 정리 구간

 

finally()는 then/catch로 처리된 결과가 성공이든 실패든 상관없이 "마지막에 반드시 실행"되는 정리 단계입니다.

 

 

1-1. 언제 쓰나

  • 로딩 스피너 종료, 버튼 비활성화 해제
  • 리소스 정리(타이머 해제, AbortController 정리, 파일 핸들 닫기 등)
  • 로그/측정(성공 여부와 무관하게 종료 시점 기록)

 

 

1-2. 중요한 규칙 3가지

  1. finally는 값을 바꾸기보다 "정리"에 집중하는 게 안전합니다.
  2. finally에서 return을 하더라도, 보통은 기존 값이 유지됩니다(값 변환 목적이 아님).
  3. finally에서 에러를 던지거나, 실패하는 Promise를 반환하면 "원래 결과를 덮어쓰고" 실패가 됩니다.
    • 즉, 정리 단계가 실패하면 전체 체인이 실패로 바뀔 수 있습니다.

 

 

1-3. 예시 (가장 흔한 형태)

setLoading(true);

fetch("/api/data")
  .then(r => r.json())
  .then(data => {
    // 성공 처리
  })
  .catch(err => {
    // 에러 처리
  })
  .finally(() => {
    setLoading(false); // 무조건 정리
  });

 


2) 네 가지 조합 API 한눈에 비교

 

2-1. 동작 요약

  • Promise.all : 전부 성공해야 성공. 하나라도 실패하면 즉시 실패(빠른 실패).
  • Promise.allSettled : 전부 끝날 때까지 기다리고, 성공/실패 결과를 "리스트로" 받음.
  • Promise.race : 제일 먼저 끝난 것(성공/실패 포함)의 결과를 그대로 반환.
  • Promise.any : 성공이 하나라도 나오면 그 성공으로 끝. 전부 실패하면 실패(묶음 에러).

 

 

2-2. 어떤 걸 선택해야 하나

  • "다 성공해야 의미 있다" → all
  • "실패해도 일단 전부 결과가 필요하다(리포트/집계)" → allSettled
  • "가장 빨리 끝난 결과를 쓴다(타임아웃, 미러 서버 등)" → race
  • "여러 후보 중 하나만 성공하면 된다(미러/대체 경로)" → any

 


 

3) Promise.all 실전 패턴

 

3-1. 병렬 처리로 성능 올리기 (대표 사례)

서로 독립적인 요청은 순차 await보다 병렬이 확실히 빠릅니다.

const [user, plan, notices] = await Promise.all([
  fetchUser(),
  fetchPlan(),
  fetchNotices(),
]);

 

3-2. 주의: 하나 실패하면 전체가 실패한다

  • all은 "성공 올인"입니다.
  • 한 요청이 실패해도 나머지는 이미 실행 중일 수 있고, 네트워크 취소도 자동으로 되지 않습니다.

 

 

대응 팁

  • 실패 허용이 목적이면 allSettled로 전환하거나
  • 각 Promise에서 에러를 삼켜서 "항목별 실패를 값으로 바꾸는" 방식을 씁니다.
const safe = p => p.catch(err => ({ error: err }));

const [a, b, c] = await Promise.all([safe(p1), safe(p2), safe(p3)]);

 


 

4) Promise.allSettled 실전 패턴

 

4-1. "성공/실패를 포함한 전체 결과"가 필요한 경우

예: 여러 파일 업로드를 한 번에 걸고, 성공/실패 목록을 UI에 보여주기.

const results = await Promise.allSettled(files.map(upload));

const ok = results
  .filter(r => r.status === "fulfilled")
  .map(r => r.value);

const fail = results
  .filter(r => r.status === "rejected")
  .map(r => r.reason);

 

4-2. allSettled를 쓰면 좋은 상황

  • "부분 성공"도 의미가 있을 때
  • 실패한 항목만 재시도 로직을 태우고 싶을 때
  • 배치 작업의 리포팅/로그가 필요할 때

 


 

5) Promise.race 실전 패턴

 

5-1. 타임아웃 구현 (가장 흔함)

race는 "먼저 끝난 것"이 이깁니다. 그래서 타임아웃 Promise와 경쟁시키는 형태가 자주 쓰입니다.

function withTimeout(promise, ms) {
  const timeout = new Promise((_, reject) =>
    setTimeout(() => reject(new Error("timeout")), ms)
  );
  return Promise.race([promise, timeout]);
}

const data = await withTimeout(fetchData(), 3000);

 

5-2. 주의: race는 "승자만 반환"이지 "패자를 멈추게 하지" 않습니다

  • 타임아웃이 이겼다고 해서 실제 네트워크 요청이 멈추진 않습니다.
  • 진짜 취소가 필요하면 AbortController 같은 취소 메커니즘을 같이 써야 합니다.

 

6) Promise.any 실전 패턴

 

6-1. "성공 하나만 나오면 된다"

예: 여러 미러 서버 중 하나라도 응답이 오면 사용.

const fastestOk = await Promise.any([
  fetchFromMirror1(),
  fetchFromMirror2(),
  fetchFromMirror3(),
]);

 

6-2. 전부 실패하면 AggregateError

  • any는 "전부 실패" 시 여러 에러를 묶은 AggregateError로 실패합니다.
  • 디버깅/로그를 위해 에러 목록을 꺼내 보고 싶을 때가 있습니다.
try {
  await Promise.any(tasks);
} catch (e) {
  // e.errors 에 실패 원인들이 들어있는 환경이 많습니다(표준/런타임별 차이 가능)
}

 


 

7) 조합 패턴: 실무에서 자주 쓰는 "레시피"

 

7-1. "필수 + 선택" 같이 돌리기

  • 필수 작업은 실패하면 전체 중단
  • 선택 작업은 실패해도 무시하고 결과만 있으면 사용
const safe = p => p.catch(() => null);

const [profile, banner, recommendations] = await Promise.all([
  fetchProfile(),            // 필수
  safe(fetchBanner()),       // 선택
  safe(fetchRecommendations()) // 선택
]);

7-2. "부분 성공 집계 + 실패만 재시도"

const results = await Promise.allSettled(jobs.map(runJob));

const failedJobs = results
  .map((r, i) => (r.status === "rejected" ? jobs[i] : null))
  .filter(Boolean);

if (failedJobs.length) {
  await Promise.allSettled(failedJobs.map(runJob)); // 재시도도 allSettled로
}

 

7-3. "가장 빠른 성공 + 타임아웃"

  • any로 "성공"을 기다리되, race로 전체 타임아웃을 걸기도 합니다.
const result = await Promise.race([
  Promise.any([a(), b(), c()]),                 // 성공 하나만
  new Promise((_, r) => setTimeout(() => r(new Error("timeout")), 2000))
]);

 

최종 정리

  • finally는 값 변환이 아니라 "정리" 용도에 집중하는 게 가장 안전합니다. finally에서 던진 에러는 원래 결과를 덮어쓸 수 있다는 점만 기억하면 실수 확 줄어듭니다.
  • 병렬 처리 기본은 all, 결과 리포팅까지 필요하면 allSettled.
  • 타임아웃/가장 빠른 결과는 race지만 "취소가 되진 않는다"는 점을 같이 챙겨야 합니다.
  • "성공 하나만 있으면 됨"은 any, 다만 전부 실패 시 묶음 실패가 난다는 걸 고려해야 합니다.
  • 실무 조합은 보통 "필수는 all, 선택은 safe 처리", 그리고 "부분 실패를 allSettled로 수집해서 재시도"가 가장 많이 쓰입니다.

 

 

반응형
admin