Promise 상태 전이 구현하기

자체 구현된 Promise의 상태 변화 처리

실제로 Promise를 직접 구현하면서 상태 전이 로직을 어떻게 설계할지 고민해보겠습니다. 먼저 공식 문서에 정의된 Promise의 동작 방식을 분석해 보고, 이를 바탕으로 자신의 클래스를 설계합니다.

// 공식 Promise 사용 예시
new Promise((resolve, reject) => {
  resolve(1);
});

이 코드에서 알 수 있듯이 Promise는 자바스크립트 엔진 내부에 이미 구현된 클래스입니다. 따라서 우리가 구현할 MyPromise도 이와 유사한 구조를 가져야 합니다.

1. 기본 클래스 생성

class MyPromise {
  constructor(executor) {
    // 실행자 함수를 즉시 호출
    executor(this._resolve, this._reject);
  }

  // 성공 시 호출되는 메서드 (내부용)
  _resolve(value) {
    console.log('성공 데이터:', value);
  }

  // 실패 시 호출되는 메서드 (내부용)
  _reject(reason) {
    console.log('실패 이유:', reason);
  }
}

여기서 주목해야 할 점은 executor 함수가 두 개의 인수(resolve, reject)를 받는다는 것입니다. 이 둘은 함수 타입이며, 각각 성공과 실패를 처리하는 역할을 합니다. 우리는 이 두 함수를 클래스의 내부 메서드로 정의하고, 외부에서 직접 접근하지 못하도록 제한합니다.

2. 상태 관리 추가: 상태 전이 규칙 적용

Promise는 세 가지 상태를 가집니다:

  • pending: 초기 상태, 아직 결과 없음
  • fulfilled: 성공적으로 완료됨
  • rejected: 실패함

또한 상태 전이는 단방향만 허용됩니다:

  • pendingfulfilled
  • pendingrejected

반대로는 불가능하며, 이미 완료된 상태(fulfilled 또는 rejected)는 다시 변경될 수 없습니다.

이러한 규칙을 반영하기 위해 상태를 저장할 변수를 도입합니다.

class MyPromise {
  constructor(executor) {
    this._state = 'pending';     // 초기 상태
    this._value = undefined;     // 성공 시 값 저장
    this._reason = undefined;    // 실패 시 원인 저장

    // 내부 메서드로 상태 전이 제어
    const resolve = (value) => {
      if (this._state === 'pending') {
        this._state = 'fulfilled';
        this._value = value;
      }
    };

    const reject = (reason) => {
      if (this._state === 'pending') {
        this._state = 'rejected';
        this._reason = reason;
      }
    };

    // 실행자에게 내부 메서드 전달
    try {
      executor(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }

  // 성공 콜백 등록 (미래에 확장 가능)
  then(onFulfilled) {
    if (this._state === 'fulfilled') {
      onFulfilled(this._value);
    }
    return this;
  }

  // 실패 콜백 등록 (미래에 확장 가능)
  catch(onRejected) {
    if (this._state === 'rejected') {
      onRejected(this._reason);
    }
    return this;
  }
}

3. 상태 전이 테스트

const task = new MyPromise((resolve, reject) => {
  resolve(42);    // 상태: pending → fulfilled
  reject('오류 발생'); // 무시됨 (이미 상태 변경됨)
});

task.then(result => {
  console.log('결과:', result); // 출력: 결과: 42
});

task.catch(err => {
  console.log('에러:', err);
});

위 코드에서 resolve 이후 reject가 호출되더라도 상태가 이미 변경되었기 때문에 무시됩니다. 이는 상태 전이의 일관성과 비가역성을 보여줍니다.

핵심 요약

  • resolvereject는 외부에서 직접 호출되지 않도록 내부 메서드로 캡슐화
  • 상태는 pendingfulfilled 또는 pendingrejected로만 변경 가능
  • 상태 변경 후 재설정 불가
  • 에러 처리는 try/catch로 감싸서 안정성 확보

이처럼 상태 전이 로직은 Promise의 핵심 기능 중 하나이며, 이를 올바르게 구현하려면 단일 책임 원칙불변성을 유지하는 것이 중요합니다.

태그: JavaScript Promise 상태 전이 비동기 처리 클로저

7월 14일 06:43에 게시됨