⛓️

Promise 내부 — then()이 Microtask Queue에 들어가는 과정

Promise의 3가지 상태와 thenable 체이닝의 실행 메커니즘

const p = new Promise((resolve, reject) => {
  // executor: 동기적으로 즉시 실행
  resolve('done');
});

p.then(value => console.log(value)); // 'done' — 하지만 지금 바로가 아니라 Microtask에서

상태 전이

pending → fulfilled (resolve 호출)
pending → rejected (reject 호출 또는 throw)

한 번 전이하면 다시 바뀌지 않는다 (immutable). resolve를 두 번 호출해도 첫 번째만 유효.

then은 새 Promise를 반환한다

fetch('/api')
  .then(res => res.json())    // Promise 반환
  .then(data => data.name)    // 또 Promise 반환
  .catch(err => fallback);    // 또 Promise 반환

.then()이 새 Promise를 반환하기 때문에 체이닝이 가능하다. 각 then의 콜백은 이전 Promise가 fulfilled될 때 Microtask Queue에 들어간다.

async/await와의 관계

// 이 두 코드는 동일하다:
async function getData() {
  const res = await fetch('/api');
  const data = await res.json();
  return data;
}

function getData() {
  return fetch('/api')
    .then(res => res.json());
}

await는 Promise.then()의 문법적 설탕이다. await 뒤의 코드는 then() 콜백으로 변환된다. 내부적으로 generator + Promise 조합으로 구현.

에러 처리

// ❌ catch 없으면 에러가 삼켜진다 (Unhandled Promise Rejection)
fetch('/api').then(res => res.json());

// ✅ 반드시 catch
fetch('/api').then(res => res.json()).catch(err => console.error(err));

// ✅ async/await에서는 try/catch
try {
  const data = await fetch('/api');
} catch (err) {
  console.error(err);
}

핵심 포인트

1

new Promise(executor)의 executor는 동기적으로 즉시 실행된다

2

resolve() 호출 → pending → fulfilled 전이 (한 번만, 불변)

3

then()에 등록된 콜백이 Microtask Queue에 들어감

4

async/await는 Promise + generator의 문법적 설탕

사용 사례

API 호출 체이닝 — fetch().then().then().catch() 패턴 이해 Promise.all/race/allSettled — 병렬 비동기 제어

참고 자료