본문으로 건너뛰기
김신건의 로그

[Javascript] Promise

· 수정 · 📖 약 2분 · 729자/단어 #javascript #async #ecmascript
Promise, promise, 프로미스, JavaScript Promise, JS Promise

정의

Promise 는 미래의 어떤 시점에 결정될 값을 나타내는 객체다. ECMAScript 2015 (ES6) 에 도입되어 콜백 기반 비동기의 가독성과 에러 처리 문제를 해결했다.

“프로미스는 자바스크립트 비동기 처리에 사용되는 객체”, MDN

전체 메커니즘과 발전사는 비동기와 타이밍, 콜백부터 async/await까지의 발전사 글 참조.

세 가지 상태

상태의미들어가는 방법
pending대기, 아직 결정되지 않음new Promise(...) 직후
fulfilled이행 (성공)executor 가 resolve(value) 호출
rejected거부 (실패)executor 가 reject(reason) 호출 또는 throw

핵심: 한 번 fulfilled/rejected 되면 다시 바뀌지 않는다 (settled 상태). 이 불변성이 콜백 대비 가장 큰 안전성 이점.

기본 사용

const p = new Promise((resolve, reject) => {
  setTimeout(() => {
    Math.random() > 0.5
      ? resolve('성공')
      : reject(new Error('실패'));
  }, 1000);
});

p
  .then((value) => console.log(value))
  .catch((err) => console.error(err))
  .finally(() => console.log('done'));

executor 함수는 동기적으로 즉시 실행 된다. resolve / reject 는 비동기 작업 완료 시 호출되는 콜백.

인스턴스 메서드

메서드시그니처언제 호출?
.then(onFulfilled, onRejected?)두 콜백fulfilled 시 1번, rejected 시 2번
.catch(onRejected)한 콜백rejected 시
.finally(onFinally)인자 없는 콜백정착 시 무조건

모두 새 Promise 를 반환 하여 체이닝 가능.

정적 메서드, 동시성 네 형제

메서드fulfilled 시rejected 시결과
Promise.all(iter)모두 fulfilled하나라도 rejected값 배열 / 첫 사유
Promise.allSettled(iter)모두 정착절대 안 됨{status, value/reason} 배열
Promise.race(iter)가장 먼저 fulfilled가장 먼저 rejected가장 먼저 정착한 값/사유
Promise.any(iter)하나라도 fulfilled모두 rejected첫 fulfilled / AggregateError

사용 예시

// 모두 필요
const [users, posts] = await Promise.all([
  fetch('/users').then(r => r.json()),
  fetch('/posts').then(r => r.json()),
]);

// 부분 실패 OK
const results = await Promise.allSettled([f1(), f2(), f3()]);
results.filter(r => r.status === 'fulfilled').forEach(/* ... */);

// 타임아웃
await Promise.race([
  fetch('/slow'),
  new Promise((_, rj) => setTimeout(() => rj('timeout'), 3000)),
]);

// 미러 중 하나라도
await Promise.any([
  fetch('https://mirror1/x'),
  fetch('https://mirror2/x'),
]);

체이닝 메커니즘

.then 의 콜백이 반환하는 값에 따라 다음 단계의 입력이 결정된다.

반환다음 then 입력
일반 값그 값
Promise그 Promise 가 정착할 때 값
ThenablePromise 처럼 풀어서 처리
예외 throwrejected Promise

이 자동 unwrap 이 .then().then().then() 의 평탄한 흐름을 가능하게 한다.

함정과 한계

.then 두 번째 인자 vs .catch

Promise.resolve('hi').then(
  (v) => { throw new Error('oops'); },
  (err) => console.log('이건 잡힐까?'), // ❌ 안 잡힘
);

onRejected이전 단계 의 에러만 잡는다. 자기 짝 onFulfilled 에서 던진 에러는 못 잡는다.

TIP

.catch 가 안전. 이전 모든 단계의 에러를 한 번에 잡는다.

Promise 도 지옥이 될 수 있다

.then 안에서 또 .then 을 중첩하면 콜백 지옥의 사촌. 항상 평탄하게 펴자. 또는 async/await 사용.

unhandled rejection

.catch 없이 rejected 된 Promise 는 Node.js 에서 unhandledRejection 이벤트로, 브라우저에선 콘솔 경고로 노출. 운영 환경에서 누수의 신호.

process.on('unhandledRejection', (reason, promise) => {
  console.error('처리 안 된 rejection:', reason);
});

resolve 가 Promise 면 자동 추적

new Promise((resolveOuter) => {
  resolveOuter(
    new Promise((resolveInner) => setTimeout(resolveInner, 1000))
  );
});
// 외부 Promise 는 즉시 resolveOuter 호출됐지만,
// 그 안의 내부 Promise 가 정착할 때까지 fulfilled 되지 않음.

실무 예시

콜백 API 를 Promise 화

function promisify(fn) {
  return (...args) => new Promise((resolve, reject) => {
    fn(...args, (err, result) => {
      err ? reject(err) : resolve(result);
    });
  });
}

// Node.js 의 callback 스타일 fs.readFile 을 Promise 로
const readFile = promisify(fs.readFile);
const data = await readFile('config.json');

Node.js 는 빌트인 util.promisify, fs/promises 모듈로 표준 제공.

Promise.withResolvers (ES2024)

executor 밖에서 resolve / reject 를 노출하고 싶을 때.

const { promise, resolve, reject } = Promise.withResolvers();
externalEmitter.once('data', resolve);
externalEmitter.once('error', reject);
return promise;

참고

이 글의 용어 (4개)
[Javascript] 이벤트 루프javascript
정의 이벤트 루프 (Event Loop) 는 싱글 스레드 JavaScript 엔진이 비동기 작업을 처리하기 위해 돌리는 무한 루프다. 콜 스택이 비어있을 때마다 마이크로태스크 큐…
[Javascript] 콜백javascript
- 콜백 (callback): 다른 함수의 인자로 넘겨져, 받은 쪽이 적절한 시점에 호출하는 함수. 일급 함수 () 가 있어야 가능한 패턴이라 JavaScript 에서는 자연스럽…
[Javascript] async/awaitjavascript
정의 / 는 기반 비동기 코드를 마치 동기 코드처럼 쓸 수 있게 해주는 ES2017 의 문법 설탕. 본질은 Promise 그 자체, 문법만 다르다. 전체 동작 메커니즘은 글 참조…
[Javascript] Microtask Queuejavascript
정의 Microtask Queue (마이크로태스크 큐) 는 JavaScript 가 매 틱마다 완전히 비울 때까지 처리하는 우선순위 큐다. 매크로태스크 큐보다 항상 먼저 비워진다.…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기