인프런 커뮤니티 질문&답변

2scent님의 프로필 이미지
2scent

작성한 질문수

함수형 프로그래밍과 JavaScript ES6+

지연 평가 + Promise - L.map, map, take

a.then()을 return 하는 이유가 뭐고 어떻게 동작하는 건가요?

작성

·

710

5

5:18 쯤에 return a.then()을 작성하시는데 여기서 a.then을 왜 return 하는지 모르겠어요.

if (a instanceof Promise) {
  a.then(a => {
    res.push(a);
    if (res.length == l) return res;
  }
} else {
  res.push(a);
  if (res.length == l) return res;
}

그냥 이렇게 작성하면 안 되는 건가요? 그 뒤에 재귀함수로 작성하기 위해서 그런 건가요?

그리고 재귀함수에서 동작이 이해가 안 가는 게 있는데 재귀함수 recur() 안에서 return a.then()을 하면 결국 Promise를 반환하는 거니깐 거기서 재귀가 끝나게 되는 거 아닌가요? a.then() 안에서 recur()를 호출한다 하더라도 처음에 실행했던 recur()와는 별개로 동작하는 것처럼 보이는데 실행 흐름이 어떻게 되는지 궁금합니다.

제 생각에는 만약 take에 넘어오는 이터러블이 [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)]이고, take(2)를 했을 때, 재귀를 다 돌고 최종적으로 return 되는 값이 Promise.resolve(1).then(() => Promise.resolve(2).then(() => [1, 2])) 이런 식으로 될 것 같은데 이게 맞나요?

답변 7

2

아 reduce가 go1함수를 통해 비동기를 다룬다는걸 깜빡 잊었습니다.

2

유인동님의 프로필 이미지
유인동
지식공유자

중간 중간 로그를 찍으면서 확인해보세요~ :)

프로미스를 다루는 일이 헷갈리고 어렵지만 많은 연습을 거치면 쉬워져요. 

화이팅입니다. 

1

앞선 강의에서도 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] 를 출력합니다.

1

유인동님의 프로필 이미지
유인동
지식공유자

감사합니다! 

1

이해가 잘 안가네요. 결과적으로 take가 마지막엔 Promise.resolve(res)를 반환하는건데 log함수에 넣었을 때 res를 반환하는건지 잘 모르겠어요.

1

유인동님의 프로필 이미지
유인동
지식공유자

안녕하세요.

1. take() 함수의 실행결과가 즉시 promise가 되려면 return을 해주어야합니다.

2. 예상하신대로 promise를 차례대로 풀어가면서 [1, 2] 를 만들어갑니다.

감사합니다.

0

저도 같은 의문점이 들어 같은 코드로 확인해보았는데요.

제가 바르게 이해한게 맞는지 확인하고 싶어 질문드립니다.

 

- Promise 객체 5개 리스트에서 값을 꺼내오려고 합니다.

1. a.then을 리턴하지않고 res를 리턴함

2. 코드 최초 실행 시, a 객체의 promise가 pending 상태

3. 그렇기 때문에, a 객체의 promise consumer가 실행되지 않음

4.  따라서 코드 최초 실행시 res는 비어있는 리스트  (res.length==0)

5. 콘솔창에서 확인하는 시점에는 fulfilled 되었기 때문에, res에 값들이 들어가있는것을 확인할 수 있음 (res.length==5)

2scent님의 프로필 이미지
2scent

작성한 질문수

질문하기