내가 구현하고자 한것
쿠팡 상품연동페이지를 만들던 도중 문제가 발생했다.
다른 마켓과는 달리 쿠팡에서 상품의 정보를 불러오는 로직이 달랐다.
기존 : productInfo 를 요청하면, 모든 상품의 종류와 상세정보가 같이 불러와 졌다.
쿠팡 : productInfo 를 요청하면 다음과 같이 api요청을 해야한다.
- 모든 상품의 종류를 불러온다. 상세정보는 담겨있지 않고, 상품의 간단한 정보만 담겨져 있다.
- 위에서 가져온 list 를 통해, 각각의 상세정보를 요청하는 api를 써야했다. 즉, 모든 상품의 상세정보를 페이지에서 구현하려면, 상품의 갯수만큼 detailInfo 를 요청해야만 한다.
한 페이지에서 한번에 불러올 수 있는 상품의 갯수는 100개이다. 100개 이상인 경우, 100번의 request 가 있어야 한다.
내가 시도했던 것.
나는 모든상품을 볼 수 있는 productInfo 를 순회해서 각각의 요소를 가지고 request하면 되겠지 라고 간단히 생각했다.
test 의 경우 물품이 3개라 문제없이 렌더 되었지만, 상품이 100개 이상이 되니까 페이지에는 50개까지밖에 렌더되지 않았다.
forEach
문을 사용해서 순회하였고, 각 요소를 순회할 때 마다 dispatch 를 실행시키는 방식으로 코드를 구현했다…
map, foreach, filter, for 문을 사용하면 어짜피 dispatch 는 비동기로 실행되겠지만 어떠한 이유때문인지 에러가 나거나, 페이지에 모두 보여지지 않는 문제가 생겼다.
문제점
for문을 베이스로 사용하는 순회함수는, 모든요소를 순회할때까지 Stack 메모리를 증가시킨다. 즉, 할당된 Stack 메모리보다 내용이 커지게 되면 stackoverflow 문제가 발생하는 것이다.
async 로 즉 비동기로 실행되는 함수의 경우 비동기 함수가 언제 종료될지 모르는 위험한 순간이 온다. 완료순서를 제어하지 못하는 코드는 좋은코드가 아니라고 생각했다.
어떻게 해결해야될까?
한번에 100개의 dispatch 를 stack 메모리에 할당하게끔 하지말고, 1개의 요청이 완료되면 다음 dispatch 가 이루어지도록 하면 되겠다.
그렇게 하면 기존 dispatch 는 완료됨과 동시에 stack 에서 휘발되기 때문에? dispatch 가 아무리 많아도 무리없이 가능할 것 같다.
모든 productInfoList 를 큐에 넣고, 선입선출 방식인 Queue 를 활용해 하나의 dispatch 가 완료되면 다음 dispatch 를 실행시키도록 해보았다.
해결방법
항상 수도코드로 먼저 생각하고 코드를 작성해보자. 다음과 같은 로직을 생각했다.
- useEffect 를 통해, getProductInfoListDone 을 감지한다.
getDetailInfo1by1
이라는 콜백함수를 만든다. 2-1. 이 함수는 Queue 가 비워지면 종료된다. 2-2. 인자로 모든리스트가 담긴 배열(ProductList) 와, 이 배열을 복사한(참조값이 다른) temp 배열을 갖는다. 2-3. dispatch 후에,.then
체이닝을 써서 동기적으로 실행시키고, 다른컴포넌트에서도 쓰는pageStoreList
를 업데이트(useState) 한다. 2-4. 문제가 없다면, queue 에서 첫 요소를 제거한 queue 를 실행시킨다.- UseEffect 안에서
getDetailInfo1by1
함수를 실행시킨다.
이렇게 코드를 구현하면, 하나의 dispatch 가 완료되고 set함수를 통해 pageStoreList 를 업데이트 한 후에 다음 dispatch 가 진행된다. 즉, api요청이 시작되고 끝맺음을 내가 컨트롤 할 수 있게 된 것이다.
추가적으로 해야될 것( in-flight data requests)
request 가 100개정도 되고 , 실행을 비동기적으로 하다보니까 모든요청을 완료하기까지의 시간은 증가했다. 라우터를 이동하더라도, 100개까지의 요청이 완료될때까지 네트워크 요청은 계속 실행되었다. 이것을 끊어주는 작업이 필요했다.
You can use the
componentWillUnmount
lifecycle method to cancel any in-flight data requests when the component is unmounted.
componentWillUnmount
lifecycle 을 활용하자. 컴포넌트가 unmount 될때 실행되는 함수다.- useEffect 함수 안에서 익명함수를 return 하는것으로 표현한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
useEffect(() => {
let didCancel = false;
// 필요한 로직에서 didCancel 이 true 가 되면
// return 을 시켜 함수가 종료되도록 하면된다.
// 이것의 컨트롤은 컴포넌트가 언마운트 될때 실행된다.
...
if (!didCancel) getDetailInfo1by1(arr, newarr);
// unmount 되면 in-flight data request 를 중지한다.
else return;
...
return () => {
didCancel = true;
};
}, [getRequestInfosDone]);
블로깅을 마치며..
논리적으로는 나의 코드도 틀리지 않았지만, CS 나 알고리즘을 고려하지 못한 초보수준의 실수다. 이번 실수가 나에게 밑거름이 되었다. 빨리 개인공부를 많이 할 수있는 시간이 왔으면 좋겠당…
출처
본인작성(feat. 이사님)