개인의 기록
  • 소개
  • 프로젝트
  • 글
  • 링크

© 2026 newgirok

← 글 목록

Promise — 미래의 값을 다루는 방법

2025년 9월 2일
JavaScriptPromise비동기thencatch

서버에 데이터를 요청했는데 응답이 언제 올지 모릅니다. 그 사이에 화면은 멈춰 있어야 할까요? JavaScript는 이 문제를 Promise로 해결합니다.

비동기 결과를 담는 상자

Promise는 "지금 당장은 비어 있지만, 나중에 값이 채워질 상자"입니다. 상자를 먼저 건네받고, 값이 채워지면 그때 꺼내 쓰는 방식입니다. 비동기 작업의 결과를 동기 코드처럼 순서 있게 다룰 수 있게 해줍니다.

Promise의 세 가지 상태

Promise는 항상 세 가지 상태 중 하나에 있습니다.

  new Promise(...)
        │
        ▼
   [ pending ]  ── 작업 진행 중
        │
   ┌────┴────┐
   ▼         ▼
[fulfilled] [rejected]
 성공, 값 있음  실패, 이유 있음

한 번 fulfilled 또는 rejected 상태가 되면 다시 변하지 않습니다. 상태는 단방향으로만 전이됩니다.

Promise 생성

new Promise는 executor 함수를 받습니다. executor 안에서 비동기 작업을 수행하고, 성공하면 resolve, 실패하면 reject를 호출합니다.

function fetchUser(id) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (id > 0) {
        resolve({ id, name: "Alice" });
      } else {
        reject(new Error("유효하지 않은 ID입니다."));
      }
    }, 1000);
  });
}

then / catch / finally

then은 성공 결과를, catch는 실패 이유를 받습니다. finally는 성공·실패에 관계없이 항상 실행됩니다.

fetchUser(1)
  .then((user) => {
    console.log("성공:", user.name); // "성공: Alice"
  })
  .catch((err) => {
    console.error("실패:", err.message);
  })
  .finally(() => {
    console.log("요청 완료"); // 항상 실행
  });

then은 두 번째 인자로 실패 핸들러를 받을 수 있지만, 가독성을 위해 catch를 별도로 쓰는 것이 일반적입니다.

Promise 체이닝

then이 반환하는 값은 다음 then의 입력으로 전달됩니다. 이를 통해 비동기 작업을 순서대로 연결할 수 있습니다.

fetchUser(1)
  .then((user) => {
    console.log("사용자:", user.name);
    return user.id; // 다음 then으로 전달
  })
  .then((id) => {
    return fetch(`/api/posts?userId=${id}`); // Promise 반환 가능
  })
  .then((response) => response.json())
  .then((posts) => {
    console.log("게시글 수:", posts.length);
  })
  .catch((err) => {
    console.error("오류:", err.message);
  });

then에서 Promise를 반환하면 체인은 그 Promise가 완료될 때까지 기다립니다.

에러 전파

체인 중간에서 에러가 발생하면 이후 then은 건너뛰고 가장 가까운 catch로 이동합니다.

fetchUser(1)
  .then((user) => {
    throw new Error("처리 중 문제 발생"); // 의도적 에러
  })
  .then((data) => {
    console.log("이 코드는 실행되지 않습니다.");
  })
  .catch((err) => {
    console.error("catch에서 처리:", err.message);
    // "catch에서 처리: 처리 중 문제 발생"
  });

catch 이후에도 then을 연결할 수 있습니다. catch가 값을 반환하면 체인이 다시 이어집니다.

Promise.all

여러 Promise를 동시에 실행하고, 모두 성공했을 때 결과 배열을 반환합니다. 하나라도 실패하면 즉시 전체가 실패합니다.

const p1 = fetchUser(1);
const p2 = fetchUser(2);
const p3 = fetchUser(3);

Promise.all([p1, p2, p3])
  .then(([user1, user2, user3]) => {
    console.log(user1.name, user2.name, user3.name);
  })
  .catch((err) => {
    console.error("하나라도 실패:", err.message);
  });

Promise.allSettled

모든 Promise가 완료(성공 또는 실패)된 후, 각각의 결과를 수집합니다. 하나가 실패해도 나머지 결과를 잃지 않아야 할 때 사용합니다.

Promise.allSettled([fetchUser(1), fetchUser(-1), fetchUser(3)])
  .then((results) => {
    results.forEach((result) => {
      if (result.status === "fulfilled") {
        console.log("성공:", result.value.name);
      } else {
        console.log("실패:", result.reason.message);
      }
    });
  });

Promise.race

가장 먼저 완료된 Promise의 결과(성공 또는 실패)를 반환합니다. 타임아웃 구현에 자주 활용됩니다.

const timeout = new Promise((_, reject) =>
  setTimeout(() => reject(new Error("시간 초과")), 3000)
);

Promise.race([fetchUser(1), timeout])
  .then((user) => console.log("응답:", user.name))
  .catch((err) => console.error(err.message));

Promise.any

가장 먼저 성공한 Promise의 결과를 반환합니다. 모두 실패할 경우 AggregateError를 던집니다. 여러 소스 중 하나라도 성공하면 충분할 때 사용합니다.

Promise.any([fetchUser(-1), fetchUser(2), fetchUser(3)])
  .then((user) => {
    console.log("첫 번째 성공:", user.name); // fetchUser(2)의 결과
  })
  .catch((err) => {
    console.error("모두 실패:", err.errors);
  });

Promise.race는 실패도 반환하지만, Promise.any는 성공만 반환합니다.

Promise와 마이크로태스크 큐

then 콜백은 동기 코드가 모두 끝난 직후, 이벤트 루프의 다음 태스크보다 먼저 실행됩니다. 이는 then 콜백이 마이크로태스크 큐에 등록되기 때문입니다.

console.log("1");

Promise.resolve().then(() => {
  console.log("2"); // 마이크로태스크
});

setTimeout(() => {
  console.log("3"); // 매크로태스크
}, 0);

console.log("4");

// 출력 순서: 1 → 4 → 2 → 3

동기 코드(1, 4)가 먼저 실행되고, 마이크로태스크(2)가 처리된 후 매크로태스크(3)가 실행됩니다.

마이크로태스크 큐(Microtask Queue): 현재 실행 중인 스크립트가 끝난 직후 실행되는 작업 대기열로, setTimeout 같은 매크로태스크보다 우선순위가 높습니다.

정리

메서드성공 조건실패 조건용도
Promise.all전부 성공하나라도 실패모든 결과가 필요할 때
Promise.allSettled전부 완료없음성공·실패 모두 수집할 때
Promise.race가장 먼저 완료가장 먼저 실패타임아웃, 속도 경쟁
Promise.any가장 먼저 성공전부 실패하나만 성공해도 충분할 때

Promise는 비동기 작업의 성공과 실패를 명확히 분리하고, 체이닝으로 복잡한 흐름을 읽기 쉽게 만듭니다. async/await도 내부적으로 Promise를 사용하므로, Promise를 이해하면 비동기 코드 전반을 다루는 기반이 됩니다.

← 이전 글이벤트 루프 — 싱글 스레드가 동시성을 흉내내는 방법
다음 글 →비동기 프로그래밍 — JavaScript가 기다리는 방법