[Javascript] 중첩 Promise, fulfilled와 rejected, thenable

TL;DR;

  • 외부 Promise의 상태가 내부 Promise 상태에 의존하고 있으면, 내부 Promise의 상태가 결정될 때까지 외부 Promise의 상태는 settled되지 않는다.
  • resolved와 fulfilled는 다른 범주의 용어다.
  • thenable 객체는 then 메서드가 정의된 객체를 말한다.

서론

Promise에 대한 MDN 문서 설명 보다가 아래와 같은 예시 코드를 발견했다.

// Outer
new Promise((resolveOuter) = {
  resolveOuter(
    // Inner
    new Promise((resolveInner) = {
      setTimeout(resolveInner, 1000);
    }),
  );
});

위 코드에서 Promise의 상태는 대강 아래와 같은 단계를 따른다.

Outer Promise 객체는 생성되자마자 resolveOuter함수를 호출하면서 resolved 된다.
  1. Inner Promise는 pending 상태로 생성된다.
  2. 1초 후에 Inner Promise가 fulfilled 상태가 되면서 Outer Promise 역시 fulfilled 된다.

여기서 유의할 점은 resolved와 fulfilled가 동의어가 아니라는 것. 즉, resolved 되었다고 해서 Promise의 상태가 fulfilled나 rejected가 되는 것이 아니다.

문득 당연한 사실로 넘어갔던 의문이 발동했다.

fulfilled는 무엇인가?

fulfilled와 rejected의 조건

서론의 예시 코드에서 Outer Promise 객체는 분명 resolve 함수를 호출했다. resolve 함수가 호출되면 Promise는 thenable 객체를 반환하고 resolve() 함수를 호출할 때 인자로 넣어준 value가 resolved 된다. 이 상태가 되면 해당 Promise는 fulfilled 되었다고 한다.

여기서 많이 오해할 수 있는 부분은 resolve 호출이 곧 fulfilled 상태를 반드시 보장하지는 않는다는 것이다. 위 예시 코드와 같이 Promise의 상태는 내부에 있을 비동기 작업(Promise)에 의해 연기(defer)될 수 있다.

undefined

그렇다면 Promise가 fulfilled 상태가 될 수 있는 정확한 조건은 뭘까?

나처럼 용어 정의에 민감한 사람들이 더러 있었는지 MDN 문서가 친절하게 링크를 알려주었다. https://github.com/domenic/promises-unwrapping/blob/master/docs/states-and-fates.md

해당 문서를 살펴보니, Promise에는 States와 Fates가 있다.

States: pending, fulfilled, rejected Fates: resolved, unresolved

그렇다. 애초에 resolved와 fulfilled는 다른 범주였다.

fulfilled의 조건에 대해서는 이렇게 정의되어있다. 기본적으로 resolved인 Promise는 pending, fulfilled, rejected 세 가지 상태 중에 하나가 될 수 있다.

Fulfilled, if it has been resolved to a non-promise value, or resolved to a thenable which will call any passed fulfillment handler back as soon as possible, or resolved to another promise that is fulfilled.
Rejected, if it has been rejected directly, or resolved to a thenable which will call any passed rejection handler back as soon as possible, or resolved to another promise that is rejected.
Pending, if it has been resolved to a thenable which will call neither handler back as soon as possible, or resolved to another promise that is pending.

Pending 항목을 보면, pending 상태인 또 다른 Promise로 resolved 되거나 resolve 되는 thenable 객체의 fulfillment handler가 실행되지 않는 한 해당 Promise의 state는 Pending이라고 되어있다.

정확하게 서론 예시 코드에 부합하는 설명이다.

그렇다면 아래와 같이 thenable 객체로 resolve를 하면 결과는 어떻게 될까?


new Promise((resolveOuter) = {
  resolveOuter(
    // Inner
    {
      then: setTimeout(() = { console.log("fulfillment settled - Inner Promise!")},1000)
    }
  );
}).then(() = {
  console.log("fulfillment settled - OuterPromise!")
});

fulfillment settled - OuterPromise! 가 출력되고, 1초 후에 fulfillment settled - Inner Promise! 가 출력된다.

결과

undefined

thenable 객체?

간단하게 말하면, then() 메서드가 정의되어 있는 객체를 말한다. Promise도 thenable 객체의 한 종류다. 정확히는 thenable 객체의 특성에 다른 비동기적인 인터페이스를 추가한 것이 Promise 객체라고 할 수 있다.

thenable 객체는 처리되는 방식이 Promise와 같다.

const thenableObj = {
  then: function(onFulfilled, onRejected){
     // ... code ...
     onFulfilled();
  }
}

thenable 객체에 정의한 then 메서드는 아래와 같이 체이닝해서 사용이 가능하다.

const thenableObj = {
  then: function(onFulfilled, onRejected){
     // ... code ...
    onFulfilled();
  }
};

Promise.resolve(thenableObj).then(() = { console.log("I'm Thenable!")});

일반적인 Promise 객체처럼 thenable도 await 키워드를 통해 resolve가 가능하다.

const thenableObj = {
  then: function(onFulfilled, onRejected){
     // ... code ...
    onFulfilled("I'm thenable!");
  }
};

const returnValue = await thenableObj;
console.log(returnValue); // "I'm thenable!" 출력


Profile picture

Written by Kim Soon Yo

IT 생태계의 플랑크톤

Github Link