게시글
질문&답변
2023.05.16
a.then()을 return 하는 이유가 뭐고 어떻게 동작하는 건가요?
앞선 강의에서도 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 { }, Promise { } ]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] 를 출력합니다.
- 5
- 7
- 710
질문&답변
2023.05.16
질문있습니다...
흥미로운 질문이어서 저도 고민하고 코드로 돌려보다가 어느정도 이해한 것 같아 정리해서 올려봅니다.이 질문으로 고민하시는 분들께 도움이 되길 바랍니다.-----------------------------------------------먼저 중요한 개념은 프로미스를 객체로 바라보아야 하는 것 입니다. 또한 프로미스 객체를 생성할 때, 콜백함수는 즉시 실행되지만, then() 으로 생성되는 프로미스 객체의 콜백은 콜스택이 비워진 이후에 실행된다는 점을 기억해야 합니다.실행할 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]100010000reduce 내부에 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 되고(애초에 resolve된 상태로 생성되었지만), 콜스택이 비워졌으므로 microQueue 에 (2) 프로미스의 콜백이 들어가고 실행됩니다. 여기서 다시 recur 함수가 실행됩니다.recur 함수가 참조하는 값들은 모두 클로저로 유지가 되고 있기 때문에 이전과 같은 환경에서 동작이 가능합니다. 따라서 이후에 들어오는 함수 또는 acc 값은 일반 number 값이므로, while문 안에서 계속 재활용 하며 조건문을 만족할 때 까지 ( cur.done ) 진행됩니다.최종적으로 reduce는 정상적으로 acc 가 11111로 계산되고, console.log 함수에 의해 그 값이 출력됩니다.
- 1
- 4
- 367