Nexon Open Api를 통해 프로젝트 개발을 한지 일주일이 넘었네요.
하나의 페이지에서 다양한 정보를 렌더링해야 하다 보니, 같은 페이지에서 여러 API를 동시에 호출해야 하는 상황이 자주 발생했습니다.
개발하면서 겪은 고민들과 학습한 내용을 이 글을 통해 정리해보려 합니다.
현재 개발 중인 캐릭터 검색 프로젝트의 닉네임 검색 페이지입니다.

예를 들어, 팡이요라는 닉네임을 검색하면 아래와 같이 다양한 데이터를 색상별로 구분하여 각각의 API 요청으로 받아와 렌더링하게 됩니다.

처음에는 아래처럼 async/await으로 순차 처리 방식으로 데이터를 가져오도록 구현했습니다.
const getCharacterInfo = await getCharInfo(ocid)
const popularity = await getPopularity(ocid)
const getStat = await getStatApi(ocid)
const cashCody = await getCashCody(ocid)
즉, 각 요청은 이전 요청이 끝난 후에야 시작되기 때문에 전체 요청 시간이 누적됩니다.
Promise.all()은 여러 개의 프로미스를 병렬로 실행하고, 모든 프로미스가 완료될 때까지 기다린 후 결과를 반환합니다. 단, 하나라도 실패하면 전체가 실패(reject) 처리된다는 특징도 존재합니다.
즉, Promise 중 처리 시간이 가장 오래 걸리는 요청 기준으로 전체 완료 시간이 결정됩니다.
const [getCharacterInfo, popularity, getStat, cashCody] = await Promise.all([
getCharInfo(ocid), // 처리 시간 3초
getPopularity(ocid), // 처리 시간 5초
getStatApi(ocid), // 처리 시간 2초
getCashCody(ocid), // 처리 시간 6초
])
각 요청이 3초, 5초, 2초, 6초 걸린다면
=> 순차 처리 시 총 16초 소요
=> Promise.all()을 쓰면 가장 오래 걸리는 6초 안에 모든 요청 완료
Promise.all을 알아보다보면 멱등성과 비멱등성에 대한 언급이 자주 나옵니다.
Promise.all()은 병렬 요청 중 하나라도 실패하면 전체가 실패합니다.
이때, 실패한 요청을 다시 시도하거나 Promise.all() 전체를 다시 호출할 경우, 동일 요청이 여러 번 실행될 수 있습니다.
요청이 멱등하지 않으면 문제 발생 가능성이 있기 때문입니다.
같은 요청을 여러 번 보내더라도, 결과나 시스템 상태가 변하지 않는 성질입니다.
| 요청 예시 | 멱등성 여부 |
|---|---|
GET /sampleUser/123 | ✅ O |
POST /sampleUser | ❌ X |
사실 이러는 이유는?
멱등한 API를 사용하는 것이 재시도나 병렬 호출에서 안정성을 높여줍니다.
💡 일부 요청이 실패해도 가능한 데이터만 우선 보여주고 싶을 때
Promise.allSettled의 코드는 아래와 같습니다.
각 요청의 성공과 실패를 추적할수 있습니다. Promise.all과는 다르게 하나만 실패해도 모두 실패가 아닌 독립적으로 성공과 실패를 구별하기 때문에 일부는 실패해도 성공한것들은 정상적으로 실행할수있는 것입니다.
const results = await Promise.allSettled([
getCharInfo(ocid),
getPopularity(ocid),
getStatApi(ocid),
getCashCody(ocid),
])
const getCharacterInfo = results[0].status ==='fulfilled' ? results[0].value : null
const popularity = results[1].status === 'fulfilled' ? results[1].value : null
const getStat = results[2].status === 'fulfilled' ? results[2].value : null
const cashCody = results[3].status === 'fulfilled' ? results[3].value : null
모든 요청이 반드시 성공해야할때 사용하는것이 좋습니다.
일부 요청이 실패해도 가능한 것만 먼저 보여주고 싶을 때입니다.
| 항목 | Promise.all | Promise.allSettled |
|---|---|---|
| 성공 조건 | 모든 요청이 성공해야 전체 성공 | 요청 성공/실패와 무관하게 전체 완료 |
| 실패 처리 방식 | 하나라도 실패하면 전체 reject | 각 요청별 fulfilled / rejected로 나뉘어 개별 확인 가능 |
| 적합한 상황 | 모든 데이터가 필수적일 때 | 일부 데이터만 있어도 괜찮을 때 |
| 유연성 | 낮음 | 높음 |
| 코드 복잡도 | 간단하지만 실패 시 취약 | 복잡하지만 견고한 fallback 가능 |
이번 프로젝트를 진행하면서 데이터를 효율적으로 fetch하는 방법에 대해 많은 고민을 하게 되었습니다. 기존 CSR 환경에서는 React Query의 useQuery를 활용했기 때문에, 비동기 요청 처리에 대해 크게 신경 쓰지 않고도 편리하게 개발할 수 있었습니다.
하지만 서버 컴포넌트 환경에서는 data fetching을 고민할 때, 여러 API를 순차적으로 호출하는 방식이 성능에 큰 영향을 줄 수 있다는 점을 체감하게 되었습니다.
이를 통해 알게 된 것은, 여러 개의 API 요청이 서로 독립적이라면 Promise.all이나 Promise.allSettled를 활용해 병렬로 처리하는 것이 성능 면에서 훨씬 효율적이라는 점입니다. 상황에 맞는 적절한 병렬 처리를 통해 성능 개선을 해보았습니다.