모던 자바스크립트 Deep Dive | 프로미스

Jiyun Kim·2024년 12월 04일
JavaScript

45장 프로미스

들어가며

예전에 부트캠프에서 Promiseasync/await를 간단히 정리한 적이 있었는데, 이번에는 딥다이브 책을 공부하면서 다시 한 번 자세히 정리해보고자 한다.

자바스크립트에서 비동기 처리를 다루는 대표적인 패턴은 콜백이었다. 하지만 콜백 패턴은

  • 콜백 지옥(Callback Hell)
  • 에러 처리의 어려움

이라는 단점이 있었다.
이를 극복하기 위해 ES6에서 Promise가 도입되었고, 이후 비동기 프로그래밍의 표준으로 자리 잡게 되었다.


1. 프로미스의 생성

프로미스는 비동기 처리 상태와 결과를 관리하는 객체다.

const promise = new Promise((resolve, reject) => {
  // 비동기 처리 로직
  if (/* 성공 */) resolve('success');
  else reject('error');
});

프로미스는 3가지 상태(state)를 가진다.

  • pending: 비동기 처리 수행 전
  • fulfilled: 성공적으로 완료 (resolve 호출)
  • rejected: 실패 (reject 호출)

한 번 settled(fulfilled/rejected) 상태가 되면 더 이상 다른 상태로 변하지 않는다.


2. 프로미스의 후속 처리 메서드

프로미스는 처리 결과에 따라 후속 작업을 정의할 수 있는 메서드를 제공한다.

  1. then

    • 두 개의 콜백을 인수로 받는다. (성공 시, 실패 시)
    • 항상 새로운 프로미스를 반환한다.
    promise
      .then((result) => console.log(result))
      .catch((err) => console.error(err));
    
  2. catch

    • 실패(rejected) 상태만 처리한다.
    • 모든 then 이후에 배치하면 체인 전체에서 발생한 에러를 처리할 수 있다.
    promise.catch((err) => console.error(err));
    
  3. finally

    • 성공/실패 여부와 관계없이 무조건 실행된다.
    promise.finally(() => console.log("작업 종료"));
    

프로미스의 에러 처리

then의 두 번째 인자로 에러 처리를 할 수도 있지만, 일반적으로는 catch 사용이 권장된다. 이유는 가독성이 좋고, then 내부에서 발생한 에러까지 한 번에 잡을 수 있기 때문이다.

마이크로태스크 큐

프로미스 후속 처리 메서드의 콜백은 태스크 큐가 아닌 마이크로태스크 큐에 저장된다.

setTimeout(() => console.log(1), 0);

Promise.resolve()
  .then(() => console.log(2))
  .then(() => console.log(3));

실행 순서는 2 → 3 → 1 이다. 마이크로태스크 큐가 태스크 큐보다 우선순위가 높다는 점을 주의하자.


3. 프로미스 체이닝

프로미스를 활용하면 기존 콜백 패턴의 단점인 콜백헬을 개선할 수 있다.

const url = "https://jsonplaceholder.typicode.com";

promiseGet(`${url}/posts/1`)
  .then(({ userId }) => promiseGet(`${url}/users/${userId}`))
  .then((userInfo) => console.log(userInfo))
  .catch((err) => console.error(err));

체이닝을 통해 비동기 작업을 순차적으로 연결할 수 있고, 가독성이 훨씬 좋아진다. 단, 여전히 콜백 자체를 완전히 제거할 수는 없기 때문에 ES8부터는 async/await을 사용한다. 그래도 프로미스를 기본으로 하고 있으니 잘 이해하자.


4. 프로미스의 정적 메서드

Promise 객체는 정적 메서드를 제공한다.

  1. Promise.resolve / Promise.reject

    즉시 이행되거나 거부되는 프로미스를 반환한다.

  2. Promise.all 여러 개의 비동기 처리를 병렬로 처리한다.

    Promise.all([req1(), req2(), req3()])
      .then((results) => console.log(results)) // [1,2,3]
      .catch(console.error);
    
    • 모든 프로미스가 fulfilled가 되어야 완료된다.
    • 하나라도 rejected면 즉시 실패한다.
  3. Promise.race

    여러 프로미스 중 가장 먼저 처리된 결과를 반환한다.

  4. Promise.allSettled

    모든 프로미스가 settled 상태가 될 때까지 기다린 후 결과 배열을 반환한다.


5. fetch

fetch 함수는 HTTP 요청을 전송하는 Web API다.

fetch("https://jsonplaceholder.typicode.com/posts/1")
  .then((res) => res.json()) // Response 객체 → JSON 변환
  .then((data) => console.log(data))
  .catch((err) => console.error(err));
  • 항상 Promise를 반환한다.
  • 응답(Response)을 json(), text() 등의 메서드로 변환해야 한다(역직렬화).

마무리

프로미스는 단순한 문법 요소가 아니라, 자바스크립트의 비동기 처리 패러다임을 바꾼 핵심 개념이다. 콜백 패턴의 단점을 보완하면서 코드의 가독성과 에러 처리를 한층 개선해 주었다.

예로는 Promise.all을 활용한 병렬 요청, fetch를 통한 API 통신, 그리고 async/await과 함께 자연스럽게 사용된다.