"Promise 는 값의 placeholder 가 아냐. *작업의 eventual completion* 을 표현하는 객체야. Chain 이 실패하기 시작하면 그 구분이 중요해져."
세 상태
Promise 는 어느 순간 정확히 세 상태 중 하나야:
- pending — 작업 안 끝남. 값도 없고 에러도 없음.
- fulfilled — 작업 성공. Promise 가 값 보유.
- rejected — 작업 실패. Promise 가 이유 보유 (보통 Error).
Promise 는 한 번 transition: pending → fulfilled, 또는 pending → rejected. 절대 되돌아 안 가. 두 번 transition 안 함. 한 번 settle 되면 (fulfilled 또는 rejected) Promise 는 immutable — 들고 있는 값이나 이유가 안 바뀜.
.then, .catch, .finally — 세 handler
Handler 붙여서 promise 에 반응:
fetchUser(id)
.then(user => user.email) // runs on fulfillment
.then(email => sendWelcome(email))
.catch(err => logError(err)) // runs on rejection
.finally(() => closeDB()) // runs either way
각 .then 이 *새* promise 반환. .then handler 가 값 반환하면 다음 promise 가 그 값으로 fulfill. Promise 반환하면 다음 promise 가 그걸 *adopt* — 같은 상태, 같은 값. throw 하면 다음 promise 가 throw 된 에러로 reject. Chain 이 implicit dataflow 야.
Error Propagation — 단일 킬러 feature
.catch 찾을 때까지 .then handler 들 건너뛰어. 이게 promise 가 callback 이긴 이유: 에러가 기본으로 전파, 모든 레벨에 `if (err) return done(err)` 쓰는 거 요구 안 함. Chain 끝의 단일 .catch 가 어느 단계의 실패든 다 처리.함정: catch 까먹으면 Node 가
UnhandledPromiseRejection 경고 로그. Node 15+ 에선 처리 안 된 rejection 이 기본으로 프로세스 종료. 항상 chain 을 .catch 로 끝내거나, 안전망으로 process.on('unhandledRejection', ...) 써.Promise.all, Promise.race, Promise.allSettled, Promise.any
Combinator 넷, 각각 여러 promise 처리에 다른 semantics:
Promise.all([p1, p2, p3])— 모두 fulfill 하면 값 배열로 fulfill. 어느 거든 reject 하는 순간 reject. "이거 다 성공해야 해" 용.Promise.allSettled([p1, p2])— 절대 reject 안 함;{status, value|reason}배열로 fulfill. "다 해보고 뭐 성공했는지 알려줘" 용.Promise.race([p1, p2])— 가장 먼저 settle 한 거로 fulfill 또는 reject. Timeout 패턴 용.Promise.any([p1, p2])— 첫 fulfillment 로 fulfill; 모두 reject 해야만 reject. "이 mirror 중 아무거나 되면 OK" 용.
옳은 combinator 고르기가 견고한 fan-out 과 깨지기 쉬운 fan-out 의 차이야.
Promise 직접 만드는 일 드물어
2026 Node 에선 new Promise(executor) 거의 안 불러. stdlib 의 fs/promises, dns/promises, timers/promises 가 이미 promise 반환. util.promisify 가 callback API 너 대신 감싸. Promise 직접 짜는 몇 안 되는 경우는 event-emitter 를 promise-land 로 다리 놓을 때고, 그것도 보통 helper 있어.
new Promise((resolve, reject) => { ... }) 자주 쓰고 있으면, 이미 존재하는 helper 를 재발명하고 있는 건 아닌지 의심해.
Pippa 의 고백
const p = doThing() 이 doThing 을 나중에 일어나게 하는 게 아냐 — doThing 은 이미 진행 중; p 는 그걸 잡는 손잡이. Promise 는 observer 지 scheduler 가 아냐. 그 모양 잡고 나니 chain 이 "단계" 가 아니라 "반응" 이 됐어.