[ECMAScript6] 성공적인 Promise는 중첩되지 않는다.

ES6 Promise에는 독특한 특징이 있는데, 지난번 글에서는 설명할 타이밍을 잡지 못해서 그냥 넘어갔었다. 이번에 그 특징에 관해 설명하도록 하겠다.

전에 모나드에 관해서 설명하면서 모나드의 가장 기본적인 operator 중 하나인 bind operatorM[T] 타입의 모나드가 T 타입의 인자를 받아서 M[U] 타입의 값을 리턴하는 함수를 인자로 받아서 M[U] 타입의 모나드로 타입을 진행시킨다1)고 하였다. 하지만 ES6 Promisethen 함수에 관해서 설명하면서 then 함수가 받는 콜백이 값을 리턴하면 resolved 된 Promise가 리턴되고, 값을 throw 하면 rejected 된 Promise가 리턴된다고 하였다. 즉, then 함수만으로는 모나드를 리턴하는 함수를 통해서 타입을 전진시키는 bind operator를 구현할 수 없으므로 완전한 모나드를 구현하지 못한다. 그렇다면 ES6의 Promise는 어떻게 Promise를 전진시킬까?

간단하다. 그냥 then 함수가 인자로 받는 콜백은 Promise를 리턴해도 된다.

사실 Promise가 모나드라는 것을 생각하면, 이쪽이 올바른 사용 법이다. 하지만 ES6뿐 아니라 다른 모나드 구현체에서도 bind operator뿐 아니라, 모나드가 아닌 값을 리턴하는 함수. 즉, (M[T], T => U) => M[U]에 해당하는 함수도 구현한다. 이는 사실 내부적으로 unit operator와 bind operator를 호출하기 때문에 굳이 필요한 함수는 아니다. 그러함에도 이 함수가 존재하는 이유는 실제로 이 구현을 사용하는 경우가 일반적인 bind operator를 사용하는 경우보다 많아서 사용자의 편의를 위해서 제공되는 것일 뿐이다.

그래도 보통은 둘을 같은 이름의 함수로 구현하지는 않고, 다른 이름의 함수로 구현한다. ES6에서는 then 함수가 두 가지 일을 한다. 동적 타입 언어의 특징을 최대한 활용한 것이다.

하지만 then 함수처럼 콜백 함수가 리턴하는 것이 Promise인지 아닌지에 따라서 동작이 달라지면 실수하기 쉽다. 이것을 ES6는 애초에 Promise가 중첩되지 못하도록 함으로써 동작에 일관성을 취했다. 즉, PromisePromise를 값으로 지니지 못한다.

Promise가 중첩되지 못하게 하는 역할은 Promise.resolve 함수가 한다. resolve 함수의 스펙을 보면 resolve 함수는 받은 인자가 Promise이고 같은 생성자를 가진다면, 받은 인자 그 자체를 리턴한다고 되어 있다.2) then 함수는 내부적으로 실행된 결괏값을 이용하여 resolve 함수를 호출한 뒤 그 결괏값인 Promise를 돌려준다.

resolve 함수는 then 함수뿐이 아니라 Promise.all 함수에서도 사용된다. 지난번에 all 함수를 설명할 때는 아직 resolve 함수에 대해서 제대로 설명하지 않아서, Promise의 iterable을 받아 하나의 Promise로 zip 한다고 하였다. 하지만 all 함수도 내부적으로 아이템들을 전부 resolve 함수에 넘기기 때문에, iterable에 Promise가 아닌 다른 값을 넣어서 호출할 수 있다.


1) 말로 풀어쓰려니 복잡한데, 기호로 표현하면 아래와 같다.

(M[T], T => M[U]) => M[U]

2) 스펙 문서 3번 스텝


댓글

이 블로그의 인기 게시물

[C++] enum class - 안전하고 쓰기 쉬운 enum

RAII는 무엇인가

Log Aggregator 비교 - Scribe, Flume, Fluentd, logstash

[Python] cache 데코레이터로 최적화하기

[Web] SpeechSynthesis - TTS API