Promise 동시성 제어

고로켕·2025년 11월 30일

데이터패칭

목록 보기
2/2

직렬 처리


첫 번째 요청의 결과가 두 번째 요청에 필요한 경우 순차적으로 처리해야 한다.

async function getUserThenPosts() {
  // 1단계: 현재 로그인한 사용자 정보 가져오기
  const userResponse = await fetch('/me');
  const user = await userResponse.json(); // 예: { id: 10, name: 'Alice' }

  // 2단계: 해당 사용자의 게시글 목록 가져오기
  const postsRes = await fetch(`/users/${user.id}/posts`);
  const posts = await postsRes.json();

  console.log(posts);
}

각 단계가 이전 단계의 결과에 의존할 때 사용한다. await 키워드로 각 비동기 작업이 완료될 때까지 기다린 후 다음 단계로 진행하여 절차적이고 예측 가능한 실행 흐름을 보장한다.



병렬 처리


두 요청이 서로 독립적이라면 동시에 실행하여 전체 소요 시간을 줄일 수 있다.

const userPromise = fetch('/user').then(res => res.json());
const productPromise = fetch('/product').then(res => res.json());

userPromise.then(user => handleUser(user));
productPromise.then(product => handleProduct(product));


두 요청을 동시에 시작하지만 실행 순서는 보장되지 않는다. 네트워크 상황에 따라 어느 요청이 먼저 완료될지 예측할 수 없으므로 실행 순서가 중요한 경우에는 적합하지 않다.



Promise 유틸리티 메서드


Promise.all()

특징

모든 Promise 가 성공해야 이행되며, 하나라도 실패하면 즉시 전체가 거부된다.

  • 모든 요청이 성공적으로 완료될 때까지 기다린다
  • 하나의 요청이 빨리 완료되어도 가장 느린 요청이 완료될 때까지 기다린다
  • 하나라도 실패하면 즉시 전체가 reject 된다
  • 결과는 요청한 순서대로 배열로 반환된다

예시

  • 모든 데이터가 필수적이고, 하나라도 실패하면 전체 작업을 중단해야 할 때
async function fetchProductPage(productId) {
  const [info, stock, reviewSummary] = await Promise.all([
    fetch(`/api/products/${productId}`).then(r => r.json()),
    fetch(`/api/products/${productId}/stock`).then(r => r.json()),
    fetch(`/api/products/${productId}/reviews/summary`).then(r => r.json()),
  ]);

  return { info, stock, reviewSummary };
}

동기성 / 비동기성

// 이미 이행된 프로미스 배열
const resolvedPromisesArray = [Promise.resolve(33), Promise.resolve(44)];

const p = Promise.all(resolvedPromisesArray);
console.log(p); // Promise { : "pending" }

setTimeout(function () {
    console.log(p); // Promise { : "fulfilled", : Array[2] }
});
  • 배열 안 프로미스들은 이미 fulfilled 상태지만, Promise.all이 반환한 프로미스 p는 현재 콜스택이 비워진 뒤에야 이행된다.
  • 따라서 첫 번째 console.log 에서는 pending, setTimeout 이후에야 fulfilled가 찍힌다.


빈 iterable 인 경우에만 동기적으로 이행된다.

const p = Promise.all([]); // 즉시 이행
console.log(p); // Promise { : "fulfilled", : Array[0] }
  • 인자로 넘긴 이터러블이 비어 있으면 기다릴 프로미스가 없으므로, Promise.all([])는 그 즉시 이행 상태의 프로미스를 반환한다.

실패 우선성

하나라도 거부하면 즉시 거부한다:

const p1 = new Promise((resolve) => setTimeout(() => resolve("하나"), 1000));
const p2 = new Promise((resolve) => setTimeout(() => resolve("둘"), 2000));
const p3 = new Promise((resolve, reject) => reject(new Error("거부")));

Promise.all([p1, p2, p3])
    .then(values => console.log(values))
    .catch(error => console.log(error.message)); // "거부"


각 프로미스에 catch 를 추가하면 동작을 변경할 수 있다.

Promise.all([
    p1.catch(error => error),
    p2.catch(error => error),
])
.then(values => {
    console.log(values[0]); // "하나"
    console.log(values[1]); // "둘"
});

Promise.allSettled()

특징

모든 Promise 의 성공/실패 여부와 관계없이 모두 완료될 때까지 기다린다.

  • 각 Promise 의 상태 (fulfilled/rejected) 와 결과를 모두 반환한다
  • 모두 실패해도 reject 되지 않는다
  • 결과 객체는 statusvalue(성공) 또는 reason(실패) 을 포함한다
  • Promise.all 보다 더 정교하게 비동기 관리를 할 수 있다

예제

const values = await Promise.allSettled([
    Promise.resolve(33),
    new Promise((resolve) => setTimeout(() => resolve(66), 0)),
    99,
    Promise.reject(new Error("an error")),
]);

console.log(values);
// [
//   {status: "fulfilled", value: 33},
//   {status: "fulfilled", value: 66},
//   {status: "fulfilled", value: 99},
//   {status: "rejected", reason: Error: an error}
// ]

Promise.race()

특징

가장 먼저 완료 (이행 또는 거부) 되는 Promise 의 결과를 반환한다.

  • 가장 먼저 완료된 Promise 의 상태를 따른다
  • 빈 iterable 을 전달하면 영원히 대기 상태가 된다
  • 프로미스가 아닌 값이 포함되면 첫 번째로 등장하는 값을 결과로 이행한다
  • 특정 프로미스가 가장 먼저 reject되어도 즉시 그 에러를 반환한다

예제

var p1 = new Promise((resolve) => setTimeout(() => resolve("하나"), 500));
var p2 = new Promise((resolve) => setTimeout(() => resolve("둘"), 100));

Promise.race([p1, p2]).then(value => {
    console.log(value); // "둘" - p2가 더 빠름
});


reject 상황

var p3 = new Promise((resolve) => setTimeout(() => resolve("셋"), 500));
var p4 = new Promise((resolve, reject) => 
    setTimeout(() => reject(new Error("넷")), 100)
);

Promise.race([p3, p4]).then(
    value => console.log(value),
    error => console.log(error.message) // "넷" - p4가 더 빠름
);

Promise.any()

특징

가장 먼저 성공한 Promise 의 결과를 반환하며, 모든 Promise 가 실패해야만 거부된다.

  • 첫 번째로 이행되는 Promise 를 반환한다
  • 실패한 Promise 는 무시하고 성공할 때까지 기다린다
  • 모든 Promise 가 실패하면 AggregateError 와 함께 거부된다
  • 빈 iterable 을 전달하면 거부된다
  • 다양한 시도를 통해 하나만 실행되면 되는 시나리오에서 사용된다 (ex: 다중 CDN 주소에 대해 리소스 호출)

예제

const pErr = new Promise((resolve, reject) => reject("Always fails"));
const pSlow = new Promise((resolve) => setTimeout(resolve, 500, "Done eventually"));
const pFast = new Promise((resolve) => setTimeout(resolve, 100, "Done quick"));

Promise.any([pErr, pSlow, pFast]).then(value => {
    console.log(value); // "Done quick" - pFast가 가장 먼저 성공
});

모두 실패하는 경우

const failure = new Promise((resolve, reject) => reject("Always fails"));

Promise.any([failure]).catch(err => {
    console.log(err); // AggregateError: No Promise in Promise.any was resolved
});



비교 정리표


메서드정상 완료 조건실패 (reject) 조건주요 사용 사례
Promise.all()모두 성공하나라도 실패하면 즉시 reject모든 데이터가 필수일 때
Promise.allSettled()모두 완료 (성공/실패 무관)reject 없음 (항상 fulfill)부분적 실패를 허용할 때
Promise.race()가장 먼저 완료된 것가장 먼저 완료된 것이 실패면 reject타임아웃 구현, 빠른 응답 우선
Promise.any()가장 먼저 성공한 것모두 실패해야 reject대체 경로가 있을 때 (fallback)

0개의 댓글