js는 next()와 flatten()을 내놓아라
JavaScript에서 Promise의 then 메서드는 비동기 작업을 관리하고, 순차적으로 실행되도록 설계된 주요 기능이다. Promise는 기본적으로 비동기 작업의 성공 또는 실패를 나타내는 일종의 컨테이너로, 비동기 작업이 완료되었을 때의 결과를 관리한다. then 메서드는 이러한 Promise의 결과를 받아, 후속 작업을 연결(chain)하는 역할을 수행한다. 이를 통해, 복잡한 비동기 처리를 직관적으로 표현할 수 있게 된다.
함수형 프로그래밍의 관점에서 Promise는 모나드(monad)로 해석될 수 있다. 모나드는 특정 데이터 구조에 대한 일관된 동작 방식을 정의하며, 중첩된 구조를 평평하게(flatten) 만드는 기능을 제공한다. JavaScript의 Promise는 then 메서드를 통해 중첩된 Promise를 자동으로 평평하게 만드는데, 이는 모나드의 성질을 보여준다. 이는 비동기 작업을 중첩되지 않고, 하나의 흐름으로 이어지게 하기 위한 의도로 설계된 기능이다. 모나드는 중첩된 구조를 해결하는 데 집중한다. 예를 들어,
Promise.resolve(5)
.then( ( ) => Promise.resolve(8) )
.then( result => console.log(result) )
에서 첫 번째 then 메서드는 값을 변환하면서 새로운 Promise를 반환하고, 두 번째 then 메서드는 자동으로 중첩된 구조를 평평하게 만든다. 이처럼 모나드는 비동기 작업의 중첩을 해결하고, 단일한 결과를 반환하는 기능을 수행한다.
한편, 펑터(functor)는 구조를 유지하면서 내부 값을 변환하는 역할을 한다. JavaScript의 배열에서 사용하는 map 메서드가 대표적인 예이다.
let doubled = [1, 2, 3, 4].map( x => x * 2 )
위 예시에서, map은 배열의 각 요소를 변환하지만 배열이라는 구조를 유지하고 있다. 이렇게 펑터는 특정 구조를 유지하면서 변환 로직만을 수행한다. Promise의 then은 array의 map처럼 또한 펑터의 목적도 수행한다. then 메서드는 비동기작업을 연속적으로 지정하여서 연결(chain)을 지원하는 것을 목적으로 구성되었다. 이를 위해서 then 메서드는 Promise를 Promise로 반환하면서 구조를 유지한다. 그래서 펑터의 목적을 수행하는 것을 보여준다. 그러나 앞서 소개한대로, then은 단순히 값을 변환하는 것뿐만 아니라 중첩된 구조를 자동으로 해제하는 기능도 제공하여 모나드의 성질을 보여준다. 이로 인해 then 메서드는 모나드의 평평화(flatten) 작업과 펑터의 구조 유지 역할을 모두 수행하게 된다.
then 메서드의 이러한 두 가지 역할은 사용자에게 혼란을 초래할 수 있다. 특히, then이라는 메서드 이름은 작업의 연결을 나타내어 의도된 목적을 달성하며 펑터의 성질을 보여주지만, 내부적으로 중첩된 구조를 해결하는 모나드의 역할도 수행하기 때문에 개발자 입장에서 명확한 역할의 구분이 어렵다. 또한, then은 JavaScript의 다른 연결이 가능한 메서드들(map, filter, reduce 등)과 유사한 방식으로 설계되었기 때문에, Promise의 모나드적 성질을 직관적으로 이해하기 어렵게 만든다. 비동기 작업의 연속적인 처리와 중첩 해제를 위한 단일 메서드 체계를 유지하려는 설계 의도가 then의 역할을 모호하게 만들었다. 이로 인해 then 메서드는 사용자가 이해하기 어렵고 혼란스러운 존재가 되었다.
이러한 혼란을 줄이기 위해, Promise의 성질과 목적을 분리하여 표현하는 것이 필요하다. 펑터의 목적은 단순히 값을 변환하면서 구조를 유지하는 것이며, 이를 위한 메서드 이름으로 next가 적절할 수 있다. 반면, 모나드의 의미는 중첩된 구조를 평평하게 만드는 것이므로, 이를 수행하는 메서드로 flatten과 같은 이름이 더 명확할 것이다. 이러한 분리를 통해 각 메서드의 역할을 더욱 직관적으로 이해할 수 있게 된다.
기존의 then 메서드는 이렇게 동작한다
// 기존 then 메서드 사용
let result = Promise.resolve(5)
.then( ( ) => Promise.resolve(8) )
.then( x => console.log(x) ); // 혼합된 역할
이를 목적과 의미로 분리하자면
// 가상의 next와 flatten 메서드
let result = Promise.resolve(5).next( ( ) => 8 ); // 펑터적 변환
result.flatten().next( x => console.log(x) ); // 중첩 해제 후 결과 처리
현재 JavaScript의 Promise와 then 메서드는 다양한 고민 끝에 설계된 결과이지만, 이는 많은 개발자들에게 혼란을 초래하는 부분도 있다. 단순한 비동기 흐름을 이해하는 데 과도한 인지 부담을 주기도 한다. 이를 해결하기 위해, 더 단순하고 명확한 메서드 설계와 구분이 필요하다. Promise의 then 메서드는 유용하지만 그 역할이 혼합되어 있다. 이러한 복잡성을 줄이기 위해, 메서드의 성격와 목적을 명확하게 분리하는 방안을 고려하는 것이 필요하다고 생각한다.