[JS] 함수형 프로그래밍 (FP) _ 유인동님 강의 정리

UI SEOK YU·2023년 5월 10일
0

JavaScript

목록 보기
4/6
post-thumbnail

🟨 기초편 🟨


 함수형 프로그래밍의 장점

함수형 프로그래밍이 좋은거 왜 좋을까????

  • 사이드 이펙트를 제어할 수 있다.
    사용하는 함수를 추상화하여 순수함수로 만들어 두고, 인풋과 리턴만으로 값을 다룬다.
    또한, Mutation이 아닌 UnMutation으로 객체를 변경하지 않도록 다룬다면, 내부에서 발생하는 부수상황을 억제할 수 있다.

  • 의도를 파악하기 쉽다
    순수함수들로 이루어져 있어, 흐름과 의도를 이해하기 쉬워진다.

  • 제너레이터를 활용하여 지연평가에 용이하다.
    순수함수들을 엮어, 각 함수에 들어오는 인자를 이터레이터에서 받아온다면,
    함수로 구성된 파이프라인을 통과하여 리턴값을 받을 수 있도록 쉽게 설계가 가능하다.


Lazy 기법

Lazy 기법. 평가시점을 제너레이터를 통해 제어한다.
일반 range()는 배열에 들어갈 값들을 각가 평가해서, 그 배열의 전체를 평가하고 시작함
그러나 L.range()는 제너레이터로 이터러블 객체를 생성하여, 순회하는 작업을 만날 때, 비로소 배열에 들어갈 값 하나하나 평가를 시작함.

_.map vs L.map

지연평가를 위한 도구 : 이터레이터를 활용하는 것 똑같음, 그러나 반환값이 다름

예를들어 map 과 L.map의 경우

//map
const map = function (){
	let res = [];
	for (const p of iter){
		res.push(f(p));
	}
	return res;
};
//L.map
const L.map = function * (f, iter){
	for (const a of iter) {
		yield f(a);
	}
};

// 얘는 반환값이 배열이 아님. 
// 원소에 대한 평가만 차례대로 하고
// 일반 map처럼 반환한 배열에 대한 평가를 
// 안하니까.
  • map은 이터레이터와 함수를 받아서, 각각의 이터레이터의 원소에 함수를 먹인 값을 가지고 결과로 내놓을 배열을 ‘평가’까지 하는 것이지만,
  • L.map은 함수를 받아서 각각의 이터레이터의 원소에 함수를 먹인 값을, 자신의 이터레이터로서 내보내어야 할 값으로 평가하는 것이다. 즉 원소들만 다루고 반환 값은 배열이 아닌 제너레이터로 생성한 이터레이터 그 자체이다.

이처럼 lazy 기법은 보통의 리턴값인 배열등이 아닌,


Promise 의 활용

프로미스는 프로미스 클래스를 통해 만들어진 인스턴스를 반환하는데,

프로미스라는 값은 대기와 성공과 실패를 다루는 ‘일급 값’으로 이루어져 있다.

대기와 일을 끝내는게 코드나 컨텍스트로 만 다루는것이 아니라

‘대기 되어지는 어떤 값’을 만든다는 점에서

콜백과 가장 큰 차이를 가지고 있다.

즉 콜백에서는 비동기를 코드의 흐름으로만 구현이 되어 있지만,

프로미스에서는 ‘비동기 상황에 대한 값’을 만들어서 리턴을 한다는 것이다.

비동기로 일어난 상황을 값으로 다룰 수 있고, 값으로 다룰 수 있다는 것은 일급이라는 이야기

일급이라는건 어떤 변수에 할당되거나 함수에 전달될 수 있는 존재라는 것.

비동기로 일어난 상황을 값으로 다룬다 == 프로미스 객체임 그게


Monad 와 Kleisli

Monad

박스. 효과나 연산에 필요한 자료들이 들어 있음.

배열의 경우, 값이 없으면 전달되지 않음. 배열은 함성함수의 관점에서 값에 대한 안전한 전달을 위한 모나드임.

프로미스는 비동기 상황을 안전하게 전달하기 위한 모나드이다.

Kleisli

오류가 있을 수 있는 상황에서의 함수 합성을 안전하게 하는 규칙

외부요인에 영향을 받는 함수의 경우, 경우에 따라 같은 값을 넣어도 다르게 출력되는 경우가 존재한다.

예를들면 userId: 2 의 데이터가 존재 했는데 삭제되었을 경우, 삭제의 전과 후는 find 시 결과가 다르다.

합성함수의 관점에서, 정상적이지 않는 값을 전달하더라도, 합성되기 전과 후가 같은 결과값을 갖도록 한다.

g 가 에러가 났을 때, fg 가 에러값을 받아서 이상한 값을 출력하는게 아닌 g가 출력한 값과 동일한 값을 내놓도록 한다.

프로미스는 resolve, reject의 구분으로, 합성함수가 연속적으로 이루어져도, 발생한 잘못된 값을 추출하도록 Kleisli 형태로 동작하게 한다.


CURRY

  1. 기존의 함수 연계

  1. curry 함수 작성
  • 함수를 받는다. 그리고 새로운 함수를 리턴한다.
  • 새로 리턴된 함수는 인자를 받는다.
  • 인자가 2개 이상일 경우, 필요한 모든 인자가 충족되었으므로, f에 인자를 넣어 반환한다. ( 이때 f는 클로저로 사용이 가능하다 )
  • 인자가 1개일 경우, 즉 input a를 제외한 나머지 인자의 길이가 0 일 경우,
    인자가 새롭게 추가되어 주어지면 그걸 받아서 함수를 실행시키는 함수를 반환한다.
  • 맨 마지막 경우처럼 나누어서 진행하는 것을 currying 이라고 하며, 이는 함수가 일급 객체(=값) 이기에 가능

  1. curry를 기존의 고차함수에 적용 (map, filter, reduce 등)

  1. curry가 적용된 고차함수는 f()() 형태로 작성할 수 있다.

  1. f()() 형태로 작성할 수 있다는 것은 중복되는 input값을 생략하고 보조함수만으로 작성이 가능하다는 점이다.

Curry의 활용

_.strMap 이 어떻게 _.curry()를 활용할 수 있을까?

_.curry(_.pipe(L.map, string))_.pipe(L.map, string) 으로부터 반환된 함수
a ⇒ go (a, L.map, string) 에 대해 curry를 적용하겠다는 의미이다.

그런데 커링은 애초에 input 값이 2개 이상일 때, 함수의 평가를 구분해서 동작하게 하는 기법인데,

여기서는 input값이 하나밖에 보이지 않는다. (_.pipe 정의에 의해)

하지만, _.pipe에 전달되는 L.map 함수는 이항 함수다.

즉, 맵핑할 함수와 함수에 들어갈 인자들을 담은 값을 받는다.

따라서 함수만 전달하게 된다면, L.map에서 사용하는 함수에 해당 인자가 전달되는 것이고,

아직 imgs에 해당하는 이터러블 객체를 받지 않는 상태인 것이다.

여기서는 curry를 입혔지만, 필요한 인수 2개를 모두 받았으므로 즉시 실행된다.


비동기와 return

인프런 질문 (reduce와 비동기에서 return 여부)

https://www.inflearn.com/questions/202948/%EC%A7%88%EB%AC%B8%EC%9E%88%EC%8A%B5%EB%8B%88%EB%8B%A4

나의 답변

흥미로운 질문이어서 저도 고민하고 코드로 돌려보다가 어느정도 이해한 것 같아 정리해서 올려봅니다.
이 질문으로 고민하시는 분들께 도움이 되길 바랍니다.

먼저 중요한 개념은 프로미스를 객체로 바라보아야 하는 것 입니다. 또한 프로미스의 실행은 콜스택이 비워진 이후에 실행된다는 점을 기억해야 합니다.

실행할 go :

go(1,
    a => a + 10,
    a => Promise.resolve(a + 100),
    a => a + 1000,
    a => a + 10000,
    console.log
)

1. return을 붙이지 않았을 경우

const reduce = curry((f, acc, iter) => {
    if (!iter) {
        iter = acc[Symbol.iterator]()
        acc = iter.next().value;
    } else {
        iter = iter[Symbol.iterator]()
    }

    return go1(acc, function recur(acc) {
        let cur
        while (!(cur = iter.next()).done) {
            const a = cur.value
            console.log(acc)
            acc = f(acc, a)
            if (acc instanceof Promise) 
                acc.then(recur) // return 여부에 따른 변화
        }
        return acc;
    }
    )
})
1
11
Promise { 111 }
[object Promise]1000
[object Promise]100010000

[object Promise]100010000

reduce 내부에 console.log를 작성하여 acc 값이 어떻게 변화하는지 확인해 보았습니다.

return을 하지 않게되면, go1 내부의 while 문이 돌면서 이전에 사용했던 acc를 그대로 가져가게 됩니다.

acc는 f(acc, a) 이므로 전 단계의 acc 가 프로미스 객체이거나,
또는 a 함수가 프로미스 객체를 반환하는 함수라면 f(acc,a)는 프로미스 객체를 반환하게 되고,
이번 단계의 acc 는 프로미스 객체가 될 것 입니다.

코드에서 acc.then(recur) 은 단순히 프로미스 객체를 생성만 하는 것입니다.
여기서는 recur 함수가 실행되지 않고, 콜스택이 비워진 이후에 실행됩니다.
그러나 reduce 함수가 종료되지 않았으므로, 콜 스택이 비워지지 않습니다.
따라서 acc와 acc.then() 프로미스 객체 모두 resolve 되지 못한 채 while문이 진행됩니다.

그 말은 then()으로 생성된 promise가 resolve 되는 것을 기다리지 않고,
(콜스택이 비워져야 비로소 프로미스가 실행되어 fulfilled 된 값이 반환되므로)
다음 while 단계로 프로미스 객체를 넘겨 반복문을 이어가는 것입니다.
즉, 다음 while문에서 acc 값이 받는 값은 'fulfilled 되어 111로 resolve된 상태' 의 Promise 객체 입니다. (then()으로 생성된 프로미스가 실행된 것이 아님)

따라서 go 의 두번 째 함수처럼 중간에 프로미스를 반환하는 인자가 있을 경우,
while문은 프로미스가 해결되는 것을 기다리지 않은 채 프로미스 객체 자체를 직접 활용하게되고
다음함수인 a => a + 1000 는 이 프로미스 객체를 string 처럼 인식하여
Promise { 111 } 에 다가 '1000' 이라는 string 값을 붙여버리게 된 것입니다.

2. return 을 해주었을 경우

const reduce = curry((f, acc, iter) => {
    if (!iter) {
        iter = acc[Symbol.iterator]()
        acc = iter.next().value;
    } else {
        iter = iter[Symbol.iterator]()
    }

    return go1(acc, function recur(acc) {
        let cur
        while (!(cur = iter.next()).done) {
            const a = cur.value
            console.log(acc)
            acc = f(acc, a)
            if (acc instanceof Promise) 
                return acc.then(recur) // return 여부에 따른 변화
        }
        return acc;
    }
    )
})
1
11
111
1111
11111

11111

반면에 return을 해주었을 경우
while문이 돌고 있던 go1 함수 자체가 종료되고,
프로미스 객체인 acc...(1) 가 resolve 되면 recur 함수를 실행하겠다는 새로운 프로미스 객체...(2) 를 생성하여 값으로 반환합니다.

이 반환된 값은 결국 reduce의 반환 값이며,
reduce가 실행되던 콜스택은 일시적으로 비워집니다.

(1)..부분의 acc 프로미스 객체 객체가 resolve되고,
콜스택이 비워졌으므로 microQueue 에 (2)..부분의 프로미스의 콜백이 들어가고 실행됩니다.
여기서 다시 recur 함수가 실행됩니다.

recur 함수가 참조하는 값들은 모두 클로저로 유지가 되고 있기 때문에 이전과 같은 환경에서 동작이 가능합니다.

따라서 이후에 들어오는 함수 또는 acc 값은 일반 number 값이므로,
while문 안에서 계속 재활용 하며 조건문을 만족할 때 까지 ( cur.done ) 진행됩니다.

최종적으로 reduce는 정상적으로 acc 가 11111로 계산되고,
console.log 함수에 의해 그 값이 출력됩니다.


인프런 질문 2 (take의 비동기에서 return 여부)

https://www.inflearn.com/questions/19410/a-then-%EC%9D%84-return-%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0%EA%B0%80-%EB%AD%90%EA%B3%A0-%EC%96%B4%EB%96%BB%EA%B2%8C-%EB%8F%99%EC%9E%91%ED%95%98%EB%8A%94-%EA%B1%B4%EA%B0%80%EC%9A%94

앞선 강의에서도 return 관련 질문이 있어서 혼자 고민해보았는데
여기도 비슷한 질문이 있어 답변을 남겨봅니다. 도움이 되시길 바랍니다.

나의 답변

go 함수 입력 값 :

go([Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)],
    Lmap(a => a + 10),
    take(2),
    console.log)

1. 리턴을 하지 않을 때

const take = curry((l, iter) => {
    let res = []
    iter = iter[Symbol.iterator]();
    return function recur() {
        let cur;
        while (!(cur = iter.next()).done) {
            const a = cur.value
            if (a instanceof Promise) a.then(a => {
                res.push(a);
                return res.length == l ? res : recur()
            });
            res.push(a);
            if (res.length == l) return res;
        }
        return res;
    }()
})
[ Promise { <pending> }, Promise { <pending> } ]

go에 들어 있는 Promise.resolve(1) 값은
1 로 resolve (fulfilled) 된 프로미스 객체입니다.

이 객체가 Lmap을 통과하게 되면,Lmap을 구성하는 go1 함수에 의하여 a.then()형태의 프로미스 객체로 반환되게 됩니다.

이 객체를 받은 take 함수는 while 문에서 프로미스 객체인지 판별 후 행동합니다. 프로미스가 아닌 일반 값의 경우, 바로 res 배열에 추가 됩니다. 반면에 프로미스 객체일 경우, a.then()으로 새로운 프로미스를 생성하게 됩니다. 이것을 return 하지 않으면 take 함수는 계속 콜 스택에서 동작하고 있게 되고, 프로미스 객체의 콜백함수가 실행될 수 없습니다.

따라서 while문 안에서는 res에 넣어지는 값들이 take 함수가 실행되고 있을 때는 resolve 되지 못한 객체들이 넣어지게 됩니다. 그 후 console.로 출력하였으니, res안에는 프로미스 객체가 들어있는 것으로 찍힙니다.

2. 리턴을 할 때

const take = curry((l, iter) => {
    let res = []
    iter = iter[Symbol.iterator]();
    return function recur() {
        let cur;
        while (!(cur = iter.next()).done) {
            const a = cur.value
            if (a instanceof Promise) return a.then(a => {
                res.push(a);
                return res.length == l ? res : recur()
            });
            res.push(a);
            if (res.length == l) return res;
        }
        return res;
    }()
})
[ 11, 12 ]

return을 해주게 되면, take 함수는
a.then()으로 반환된 프로미스 객체를 반환하고
종료됩니다.
go에 의해서 프로미스 객체는 다음 console.log로 전달 될 텐데, go 함수를 구성하고 있는 reduce에

if (acc instanceof Promise)
    return acc.then(recur)

이 부분에 의해서 go 자체가 프로미스를 반환하며 종료되게 됩니다.

지금까지 종료된 함수는 2개 입니다. 처음에 take가 프로미스를 반환하며 종료되고, 그 프로미스를 받은 go가 reduce에 의해 프로미스를 반환하며 종료됩니다.

이 때 비로소 콜 스택이 비워지고, take 에서 반환했던 프로미스의 콜백이 실행됩니다.

a => {
    res.push(a);
    return res.length == l ? res : recur()
});

res에 a (여기서는 Promise.resolve(1)로 부터 받은 '1')를 저장하고, 조건문을 충족하지 못했으므로, take 삼수의 recur를 호출합니다.

다음 루프에서 while 문이 종료되고, res를 반환합니다.

res를 반환하면 이것은 reduce가 생성한 프로미스의 resolve 값이 되므로, go 함수에 있던 console.log 가 실행됩니다.

console.log는 res의 값인 [11,12] 를 출력합니다.


Concurrency :: C.reduce



직렬로 평가 vs 병렬로 평가

_.reduce 와 C.reduce의 차이는 [...iter]로 제너레이터를 한번에 돌려 프로미스들을 동시에 평가 ( 프로미스의 평가 = 프로미스 객체 반환, 콜백함수 즉시실행 ) 하는데에 있다.

만약 [... ]형태로 전개하지 않는다면
reduce의 while문에서 next()를 하여 yield 로 반환하는 값을 하나씩 빼 올 것이고,
이 과정에서 프로미스는 뽑힌다면 (=평가된다면) 뽑히는 과정에서 비로소 콜백을 실행 할 것이다.
그러나 전개하지 않은 상태에서는 이런 뽑히는 과정이 순차적이므로,
'reduce 자체를 return 하여 콜백의 완료를 기다린 후 recur 재귀함수를 통해 이어서 진행하는 과정'
을 프로미스가 뽑힐 때마다 해주어야 하는 것이다.

반면에 [...] 형태로 전개하여 사용하면, yield로 반환될 모든 값이 배열에 담긴다.
이 과정에서 뽑힌 프로미스는 콜백이 바로 실행될 것이고,
만약 제너레이터에 여러 프로미스들이 존재한다면
제너레이터의 next()가 연속적으로 동작함에 따라 콜백도 주르륵 병렬되게 실행 될 것이다.

이런 프로미스를 포함한 배열을 받은 reduce는 then()을 통해서 reduce를 종료하고 프로미스를 반환한다.
콜백이 완료되면 then(recur)에 의해 재 실행된다.
여러 프로미스들이 거의 동시에 자신의 콜백을 실행시켰기 때문에,
reduce 입장에서는 전체 시간이 가장 오래걸리는 놈 만큼만 소요다는 것이다.
즉 Promise.all() 과 같은 효과가 발생하는 것이다



🟧 응용편 🟧

응용하기 중점

  • 명령형 코드를 함수형 코드로 변환한다.
  • if 를 filter 로
  • break 를 take 로
  • 할당을 map으로
  • 합산을 reduce로
  • while 을 range로
  • 부수효과를 each로

함수를 추상적으로 합성하기

💡 reduce + 복잡한 함수 + acc 보다, map + 간단한 함수 + reduce 사용!

  • 함수에 들어가는 인자의 타입을 통일하라. (그러면 함수도 더 추상적이게 되고, 재활용이 가능해진다.)
  • 만약 total, u 에 대해 u.age를 더한다는 함수를 만들어 쓰면, age가 없을 경우 쓸 수 없는 구체적인 함수가 된다.
  • 하지만 a + b 와 같이 형이 일치하는경우에는, 다양하게 활용이 가능하다.

  • reduce 만 사용할 경우, 내부에 users를 받아 u.age가 30을 넘는지 비교하고 결과값을 맞춰서 반환한다.
  • 너무 보기 어렵다
  • 한 함수에 하나의 기능만 할당하자
  • 그러면 아래 두개 중 하나 (순서만 다른거임) 로 쓸 수 있는데,
  • 유저에서 age만 받아와서, 30을 필터링하고, 합친다
  • 가 바로 나온다! 이렇게 읽고 파악하기 쉽게 작성하자.

  • find와 filter 모두 조건을 가지고 판별해서 데이터를 가져오는 것이지만,
  • find의 경우 없으면 undefined를 반환하나, filter는 빈배열을 반환한다.
  • 따라서 find를 쓰려면, 조건문을 하나 더 추가 해야하지만,
  • filter의 경우, take를 통해 필요한 만큼만 가져오는, 즉 ‘진짜’필요한 로직으로만 작성이 가능하다.

객체를 이터러블 하게 다루기

values

entries

keys

  • 이터러블 하지 않는 객체를 가지고 이터러블하게 값을 뽑아낼 지 구현하면,
  • 어떤 값이 들어와도 이터러블하게 만들어서 함수형 프로그래밍을 짤 수 있다.

함수형 프로그래밍은 리스트를 가지고 함수-함수-함수를 거져 값을 반환하는 원리로,

기존에 있던 값, 즉 변수를 최소화하고 return값을 곧바로 다음함수의 input이 되도록 장려한다.

변수를 일일히 대입하지 않고, 자료들을 한번에 처리하려면 리스트처럼 이터러블 해야하는데,

자바스크립트에서는 제너레이터라는 강력한 도구가 있다. 무엇이든지 이터러블한 존재로 만들어 낼 수 있는 것.

이를 이용해 input값을 이터러블하게 만들고 순수함수들을 거쳐서 아웃풋을 필요한 타입에 맞게 정제하여 내보낸다.


Map()은 이터러블하지만, json 형태로 추출되는 것을 지원하지 않는다. 따라서 나열된 값들을 형변환을 해주는과정이 필요하다.

indexBy로 인덱싱이 된 상태에서, 다른 함수를 통과할 때, 인덱스를 유지, 추후에 인덱스 사용이 가능

클래스를 이터러블하게 만들었다.

클래스를 이터러블하게 만든 후, 그것의 인스턴스인 coll의 내부에 들어있는요소 (여기서는 Model)을 순회하며, 각 모델의 name 속성의 값을 가져와 콘솔로그로 찍는다.

클래스는 자료와 메서드를 합친거

객체도 내부에 변수들과 메서드가 들어 잇음

객체는 클래스로 만든 인스턴스 일 수 있음

클래스의 [Symbol.iterator]를 정의하면, 인스턴스가 가진 어떤 자료를 순회하게 함

객체는 이터러블하지 않기 때문에, for…in 를 통해 키로 순회할수있음을 응용하여 값을 순회할 수 있도록 만들고

이것으로 객체를 이터러블하게 (엄밀히 말하면 키를 통한 값의 추출의 반복) 하여 내부의 자료를 순회할 수 있다.

그럼 인스턴스가 아닌 일반 객체의 경우, 해당 객체의 [Symbol.iterator]를 정의하면 안되나??

중점 : 이터러블하지 않는 존재를 이터러블 하도록 만들 수 있는 방법을 강구


비동기를 함수형 프로그래밍으로 다루기

getPayments는 비동기를 일으키는 함수.

즉 그 이후부터 전달되는 값들은 모두 “비동기 상황을 전달하는 프라미스 객체”가 된다.

await 키워드를 붙이지 않고 로그를 찍으면 Promise (Pending) 객체가 출력됨. (당연하지 객체가 안풀린 상태로 로그를 찍었으니)

근데 await을 붙이면, 프라미스 객체였던 payments에서 비동기 상황이 종료되고 값이 설정된다. 그 값을 가져오 와서 출력하므로, 3번에 걸쳐서 이터레이터가 돌다가 길이가 3이하일 때까지 조건이 있던 takeUntil의 프라미스가 종료되어 비로소

1 대입 - 1에 해당하는 payment 프로미스 객체 생성 - 프라미스객체를 받아서 takeUntil이 다시 프라미스를 반환 - 그것을 받은 flat은 Lazy가 아니므로, 평가된 값을 다 받아서 배열로 반환함.

async function job(){
  const payments = await _.go(
  L.range(1, Infinity),
  L.map(Impt.getPayments),
  L.takeUntil(({length})) => length < 3),
  _.flat);
  console.log(payments);
}
// flat은 takeAll을 사용하며, takeAll은 take를 사용함.
// 아래는 take 코드

import curry from "./curry.js";
import toIter from "./toIter.js";
import nop from "./nop.js";

export default curry(function take(l, iter) {
  if (l < 1) return [];
  let res = [];
  iter = toIter(iter);
  return (function recur() {
    let cur;
    while (!(cur = iter.next()).done) {
      const a = cur.value;
      if (a instanceof Promise) {
        return a
          .then((a) => ((res.push(a), res).length == l ? res : recur()))
          .catch((e) => (e == nop ? recur() : Promise.reject(e)));
      }
      res.push(a);
      if (res.length == l) return res;
    }
    return res;
  })();
});
  1. go() 를 통해 함수들을 연달아 수행하게 한다.

  2. 그 내부에는 L.range, L.map, L.takeUntil, _.flat이 들어가 있다.

  3. L.range, L.map, L.takeUntil은 모두 Lazy 함수이므로, 제너레이터를 생성한다. 즉, next()로 값을 요구하기 전까지는 리턴하는 값이 없다.

  4. _.flat()은 Lazy 함수가 아닌 일반 함수이다. 따라서 리턴값을 내 놓기 위해 리턴에 해당하는 배열을 모두 채워야 한다.

  5. flat이 값을 채우기 위해 takeUntil에 next()를 요청한다.

  6. takeUntil은 flat에서 주기 위한 값을 만들기 위해 map 에 next()를 요청함.

  7. map도 마찬가지로 range에 next 요청

  8. range는 next()를 통해 1을 반환한다.

  9. map은 받은 1을 가지고 Impt.getPayments를 실행시키고, 반환되는 promise를 takeUntil에 넘김

  10. takeUntil은 받은 프로미스를 해결되면 조건식에 넣어서 판단할 것이라는 새로운 프로미스를 생성하여 리턴값으로 반환함.

  11. flat은 takeUntil로 부터 받은 프로미스가 해결되면 그 값을 자신의 결과 배열에 넣을 것이라는 프로미스를 생성하여 반환함.

    (* 이때 console.log를 찍게되면, 여기서 반환되는 프로미스가 출력되는 것 )

  12. 잠시후에 프로미스 체인에 연결된 모든 프로미스가 resolve 되고, resolve 된 값은 flat의 결과 배열에 넣어짐. 끝나지 않은 상태이므로 flat은 takeUntil에 또 값을 요청함 ( flat 에서 해결된 프로미스는 (끝나지 않았을 경우) 다시 프로미스를 반환함. 즉, flat이 어떤 조건을 만족해서 더 받아오는 것이 끝나야 비로소 promise가 완전히 resolve 되고, 비로소 res를 리턴할 수 있음 - (* flat~takeAll~take 이므로 FxJS/Strict/take.js 코드 참조 )

  13. 6~12 번의 반복

  14. flat이 takeUntil에 값을 요청했는데, takeUntil이 조건문을 만족하지 못하는 값을 받은적이 있어 false로 마감되었음. 따라서 next()를 해도 반환되지 못하고, 상태는 done임.

  15. flat은 더이상 받을 값이 없으므로, 최종 프로미스가 resolve 되고 그 동안 받아서 정리해온 배열을 리턴값으로 반환함


실제 코드에서의 활용


함수형 프로그래밍으로 결제가 잘못된 것을 뽑아 취소시키는 로직 예제



필요한 함수를 이런식으로 정의해서 사용해도 괜찮을 것 같다.



=> 함수를 통해 추상화를 하고 msg를 전달함으로써, 보기 쉽게 작성됨

Section 6-5. 클래스를 대신 함수로 하는 추상화
는 여러 번 들어보는게 좋을 것 같다.

0개의 댓글