블로그로 돌아가기
Javascript

javascript promise all을 실무에서 안전하게 사용하는 방법

javascript promise all의 개념과 실무 적용 예제를 통해 동시성 처리, 오류 대응, 성능·운영 관점의 주의점을 정리합니다. 코드 예제와 체크리스트로 바로 적용하세요.

promiseasync-awaitconcurrency-controlerror-handling
javascript promise all을 사용하는 병렬 API 호출 개념을 나타내는 다이어그램

javascript promise all이 필요한 상황

javascript promise all은 여러 비동기 작업을 병렬로 실행하고 모든 결과를 한 번에 기다려야 할 때 유용합니다. 이 글에서는 javascript promise all의 핵심 개념, 실무 예제, 언제 사용하거나 피해야 할지, 성능·보안·운영 주의점을 포함해 바로 적용 가능한 체크리스트까지 제공합니다.

javascript promise all 핵심 개념

Promise.all은 전달된 프로미스 배열이 모두 이행(fulfilled)되면 결과 배열을 반환하고, 하나라도 거부(rejected)되면 즉시 거부합니다. 이 특성 때문에 동시성 이점을 얻는 대신 한 개 실패가 전체 실패로 이어지는 상황에 주의해야 합니다.

  • 동작 요약: 병렬 실행, 단일 실패 시 전체 거부
  • 반환 값: 모든 결과를 순서대로 담은 배열

javascript promise all 실무 예제

아래 예제는 여러 API를 병렬로 호출해 데이터를 조합하는 실무 패턴입니다.

// 예제: 여러 API를 병렬 호출해 사용자와 권한 데이터를 조합 async function fetchUserData(userIds) { const userPromises = userIds.map(id => fetch(`/api/user/${id}`).then(r => r.json())); const permsPromises = userIds.map(id => fetch(`/api/perm/${id}`).then(r => r.json())); // 모든 호출이 성공해야 결과를 조합 const [users, perms] = await Promise.all([ Promise.all(userPromises), Promise.all(permsPromises) ]); // 실패 시 예외 발생 return users.map((u, i) => ({ ...u, perms: perms[i] })); } // 사용 fetchUserData([1,2,3]).then(result => console.log(result)).catch(err => console.error('전체 실패:', err));

위 예제는 이해하기 쉬운 패턴이지만, 하나의 요청 실패가 전체 작업을 무효화한다는 점을 고려해야 합니다.

실패 허용과 부분 성공 처리를 위한 대안

부분 성공을 허용하려면 각 프로미스를 실패로부터 안전하게 래핑하거나 Promise.allSettled를 사용하세요.

// 실패를 허용하는 래퍼 const reflect = p => p.then(v => ({ status: 'fulfilled', value: v })).catch(e => ({ status: 'rejected', reason: e })); async function fetchWithPartial(userIds) { const promises = userIds.map(id => reflect(fetch(`/api/user/${id}`).then(r => r.json()))); const results = await Promise.all(promises); return results; // 각 항목의 성공/실패 상태 포함 }

언제 쓰면 좋고 언제 피해야 할까

  • 언제 쓰면 좋은가
    • 여러 독립적 API 호출을 병렬로 처리해 응답 시간을 줄이고 싶을 때
    • 모든 결과가 있어야만 다음 로직을 진행할 때
  • 언제 피해야 하는가
    • 일부 실패를 허용해야 하는 경우(사용자 경험상 일부 데이터만 필요할 때)
    • 외부 서비스 호출이 많은 경우, 과도한 동시 요청으로 인해 레이트 리밋에 걸릴 우려가 있을 때

판단 기준표

구분항목1항목2항목3
내용동시성 필요 여부실패 허용 여부외부 리밋 영향
내용모든 결과 필요한 개 실패로 전체 실패 허용동시 호출이 레이트 리밋에 영향 줄 수 있음

실무 체크리스트

  • Promise.all을 쓰기 전에:
    • 모든 호출이 독립적인가?
    • 하나의 실패가 전체 프로세스에 미치는 영향은 허용 가능한가?
    • 외부 API의 레이트 리밋과 동시 호출 한도를 확인했는가?
    • 적절한 타임아웃과 재시도 로직을 적용했는가?

실무 체크리스트 (단계별):

  1. 의존성 확인: 작업들 간 의존성이 없다는 것을 검증
  2. 실패 정책 결정: 전체 실패 허용 여부 결정
  3. 제한 설정: concurrency limit 또는 배치 처리 설계
  4. 오류 처리: Promise.allSettled 또는 래퍼 적용 고려
  5. 재시도/타임아웃: 각 요청에 타임아웃과 지수 백오프 재시도 추가
  6. 모니터링: 실패율/응답시간 알림 설정

성능·보안·운영 주의점

성능

  • 동시 요청 수가 많으면 네트워크/서버 리소스를 과다 사용합니다. 필요하면 concurrency limiter(n-threads 패턴)를 사용하세요.

보안

  • 외부 API 호출 시 민감 데이터가 노출되지 않도록 인증 토큰 관리를 엄격히 하고, 로그에 민감 정보가 남지 않게 주의하세요.

운영

  • 에러 로그와 재시도 메트릭을 수집해 어느 요청이 병목인지 파악하세요.
  • 대량 호출 시 레이트 리밋 초과로 인한 전체 실패를 방지하기 위해 백오프/큐잉을 적용합니다.

구현 패턴 비교표

구분항목1항목2항목3
내용Promise.allPromise.allSettled제한된 동시성 (p-limit)
내용빠르고 단순, 하나 실패 시 전체 실패부분 성공 허용, 상태별 처리 가능레이트 리밋 대응, 안전한 동시성 제어

자주 묻는 질문

Q1: Promise.all이 실패하면 왜 전체가 실패하나요? A1: Promise.all은 전달된 프로미스 중 하나라도 rejected를 반환하면 즉시 거부 상태로 바뀌기 때문에 전체가 실패로 처리됩니다. 이는 설계된 동작입니다.

Q2: 부분 실패를 허용하고 싶습니다. 어떻게 해야 하나요? A2: Promise.allSettled를 사용하거나 각 프로미스를 성공/실패로 래핑하는 방법(reflect)을 사용하면 됩니다. 그런 다음 실패 항목을 필터링해 처리하세요.

Q3: 많은 API를 동시에 호출하면 어떻게 하나요? A3: p-limit 같은 라이브러리나 자체 큐를 만들어 동시 호출 수를 제한하고, 백오프 재시도와 타임아웃을 적용해야 합니다.

Q4: Promise.all과 async/await는 어떻게 섞어 사용하나요? A4: Promise.all은 async 함수 안에서 await와 함께 사용해 병렬 실행 결과를 기다리는 패턴으로 자주 사용됩니다. 예제는 본문 예제를 참조하세요.

정리

  • Promise.all은 여러 비동기 작업을 병렬로 실행하고 모든 결과를 기다릴 때 유용합니다.
  • 하나 실패 시 전체 실패 특성 때문에 부분 실패 허용 여부를 먼저 결정해야 합니다.
  • Promise.allSettled나 래퍼, concurrency limiter로 실무 요구사항을 만족시킬 수 있습니다.
  • 체크리스트: 의존성 확인, 실패 정책 결정, 제한 설정, 오류 처리, 재시도/타임아웃, 모니터링
  • 성능·보안·운영 관점에서 레이트 리밋, 민감정보 관리, 로그/모니터링을 반드시 구성하세요.