[TIL] 2023-06-12 Promise 와 Promise.all

H Kim·2023년 6월 12일
0

TIL

목록 보기
37/69
post-thumbnail

해야 할 것

무한스크롤로 리스트를 계속해서 조회하고 있는 페이지에서 정보를 수정하고 이것을 다시 PUT 메소드를 이용해 API를 보냈다. 이러고나서는 그냥 가마니처럼 가만히 있어도 페이지를 재렌더링 한 게 아니기 때문에 내가 보낸 정보가 남아있어서 됐당^^! 하고 즐거운 마음으로 있었다.
그러나... 사수분이 말해주시길 이러면 안 된다고...
이러면 진짜 정보가 바뀌었는지 안 바뀌었는지 알 수 없기 때문에(당연함 내가 바꾼 페이지의 상태로 머물러있을 뿐 백엔드 쪽에서 처리가 되었는지 아닌지 알 길이 없음)
업데이트를 시키고 나서는 다시 GET 메소드를 통해서 그 정보를 불러와서 다시 뿌려줘야 한다고 했다.
문제는, 내가 만든 getBrand 메소드는 무한스크롤로 구현되어 있기 때문에 페이지 번호에 따라 그만큼의 rows 만 가져온다.
즉, 기본 rows 셋팅이 25개일 때, 사용자가 무한스크롤을 2번 한 다음에 정보를 수정했다면,
가져와야 하는 정보는 75개여야하지 한 번만 불러서 25개를 불러오면 안 된다.
왜냐면 사용자가 70번째를 수정했으면 불러왔을 때 자신이 수정한 정보를 보기위해 다시 무한스크롤을 해서 밑으로 내려가야 하니까.
그냥... 이건 좀 이상한 페이지이죠?!

그래서 할 일은 getBrand 라는 만들어 놓은 GET 메소드 함수를 페이지 넘버에 따라 여러 번 보내는 것이었다.

그리고 여기서부터 나의 눈동자에 물음표가 뜨기 시작했을 것...
(이제 사수분도 내가 진짜 좀 알아들을 때랑 아예 못 알아들을 때를 구분하시기 시작한 듯)


내가 짠 코드 with Promise

처음에는 설명을 듣고 Promise 통해 (겨우) 구현하였다.

	// 저장 버튼이 눌리면
    if (clickedYes) {
      // 업데이트를 먼저 하고
      await updateBrand();
      // 내가 받아오는 데이터 형태가 배열이라 전체를 넣을 배열이 필요해서 refresh 라는 빈 배열을 선언하였다.
      const refresh = [];
      // async/await 자체가 선언을 해서 할당을 하면 그것이 Promise 객체가 된다.
      for (let i = 1; i <= pageInfo.page; i++) {
        const temp = getBrand({ page: i, pageRow: 25, totalCount: 0 });
        // 그걸 돌린 값을 결과로 받아와서 refresh 배열에 넣고
        const result = await temp;
        refresh.push(result.brands);
      }
      // 2차원 배열이라서 1차원 배열로 풀어준 다음에 원하는 곳에 재할당한다.
      storesList.brands = refresh.reduce((acc, cur) => [...acc, ...cur]);
    }

일단 되기는 됐는데 문제가 있었다.
매우 느리다는 것이었다.
개발서버에서 받아오는 총 데이터의 갯수는 122개였다.
즉, 5번까지 무한스크롤로 불러올 수 있다는 거였는데 만약에 끝까지 다 내린 다음에 수정을 하면,
1-25개 -> 26-50개 -> 51-75개 -> 76->100개 -> 101-122개 순서로 불러와서 이 때마다 깜빡깜빡 거렸다.
사수분이 보고는 이거 Promise.all 로 하면 안 이럴 거라고 했다.

그래서 다시 울면서 결국 찾아봄...


Promise 와 Promise.all 예제코드 및 참고블로그

참고블로그
Promise.all 로 비동기 로직 개선하기


  • Promise
const userList = [
  { name: 'ethan', id: 1 },
  { name: "david", id: 2 },
  { name: 'john', id: 3 }
];

// 1초가 걸리는 쿼리
const getUserById = async (id) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const [user] = userList.filter(user => user.id === id)
      resolve(user)
    }, 1000)
  })
}

// 2초가 걸리는 쿼리
const getAllUsers = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(userList)
    }, 2000)
  })
}

const fetchData = async () => {
  console.time('소요시간 : ')
  const user = await getUserById(2)
  const userList = await getAllUsers()
  // 불러온 데이터를 출력하기까지 최소 3초가 소요된다.
  console.log(user)
  console.log(userList)
  console.timeEnd('소요시간 : ')
}

fetchData()

  • Promise.all
const userList = [
  { name: 'ethan', id: 1 },
  { name: "david", id: 2 },
  { name: 'john', id: 3 }
];

const getUserById = async (id) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const [user] = userList.filter(user => user.id === id)
      resolve(user)
    }, 1000)
  })
}

const getAllUsers = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(userList)
    }, 2000)
  })
}

const fetchData = async () => {
console.time('소요시간 :')
  // Promise.all() 은 실행할 비동기 태스크들이 담긴 배열을 인자로 받습니다.
  const [user, userList] = await Promise.all([getUserById(2), getAllUsers()])
  console.log(user)
  console.log(userList)
  console.timeEnd('소요시간 :')
}

fetchData()

나는 이거 보고 무슨 생각을 했냐면... 이건 이해가 됐거든?
이 코드들을 보고 이해는 했음.
근데 내 꺼에 어떻게 적용시켜야 되는지를 전~~~혀 모르겠는 거야.
왜냐면 밑의 Promise.all 을 보면 쟤네는 각각 메소드가 다르고 예제에서야 api를 보낸 건 아니지만 만약 진짜 코드였다면 서로 GET을 해 오는 api의 url이 다를 테였다.
이러면 당연히 다른 값에 셋팅이 되어도 상관없음.
근데 난 아니라구...ㅜㅜ
나는 같은 값들에 차례차례 뒤편으로 쌓여가는 구조라서 어떻게 해야 하는 거지? 너무 이해가 안 됐다.

무엇보다 Promise.all 은 순차적으로 실행되어야 하는 건에 관해서는 쓰지 말라고 되어있었다.
왜냐면 병렬처리를 하니까 먼저 끝나는 게 장땡이라서 순서보장은 안 된다는 거야.
근데 나는 아까 말했다시피 리스트의 순서가 있으니까 데이터가 차례차례 쌓여야 되는데 순서보장이 안 되면 데이터를 업데이트하고 다시 받아왔을 때 리스트의 순서가 바뀌어있지 않을까 걱정했다.
근데 사수분이 해도 된다는 거야.

Then, I started to get confused...


이해

결국 이해했는데 결론은 구조를 똑같이 맞춰주면 된다는 거였다.
어찌됐든 간에 await Promise.all()을 할 때 안에 배열 형태[]로 들어가면 된다는 것 같았다.

그러니까 내가 위에서 만든 Promise 코드의 실행되는 실제 코드의 값은 아래와 같다.

// pageInfo.page 가 3일 때,
// 이러한 형태의 반복문은
for (let i = 1; i <= pageInfo.page; i++) {
  const temp = getBrand({ page: i, pageRow: 25, totalCount: 0 });

// 실행되면 이런 모양이 될 것이다.
temp = getBrand({ page: 1, pageRow: 25, totalCount: 0 })
temp = getBrand({ page: 2, pageRow: 25, totalCount: 0 })
temp = getBrand({ page: 3, pageRow: 25, totalCount: 0 })

// 이를 await temp 로 해 오면 GET 메소드로 정보를 불러오고 이것이 result라는 값에 담긴다.
const result = await temp;
}
// 이 값을 refresh 배열에 넣는다.
refresh.push(result.brands);

// 이러면 refresh 배열의 형태는 아래와 같다.
refresh = [Array(25), Array(25), Array(25)];

위의 코드를 Promise.all 로 만들었을 때는 아래와 같다.

// 2원 배열이 담길 예정인 finalArr
    const finalArr = [];
// Promise.all 에 넘겨줄 Promise가 담길 배열
    const brandsLoop = [];
// 마찬가지로 페이지 넘버만큼 반복문으로 Promise를 만들어준다.
    for (let i = 1; i <= this.pageInfo.page; i++) {
      brandsLoop.push(getBrand({ page: i, pageRow: 25, totalCount: 0 }));
    }

// 이는 이와같은 형태로 실행 될 것이다.
brandsLoop.push(getBrand({ page: 1, pageRow: 25, totalCount: 0 }))
brandsLoop.push(getBrand({ page: 2, pageRow: 25, totalCount: 0 })
brandsLoop.push(getBrand({ page: 1, pageRow: 25, totalCount: 0 }))

// 이러면 brandsLoop의 형태는 아래와 같다.
brandsLoop = [
  getBrand({ page: 1, pageRow: 25, totalCount: 0 }),
  getBrand({ page: 2, pageRow: 25, totalCount: 0 }),
  getBrand({ page: 3, pageRow: 25, totalCount: 0 })
]

// 콘솔에는 아래와 같이 나온다.
brandsLoop = [Promise(object), Promise(object), Promise(object)];

// 이를 result에서 Promise.all 로 병렬실행하면 속도가 빨라짐!
    const result = await Promise.all(brandsLoop);

내가 진짜 이해가 안 갔던 건 병렬처리인데 어떻게 리스트의 순서를 보장할 수 있는가? 였는데,
이렇게 풀어서 보니까 이해가 갔다.
어차피 내가 반복문으로 페이지 1, 페이지 2, 페이지 3 이렇게 불러왔잖아.
그리고 내가 이 데이터를 받아서 풀어낼 때도 어차피 앞부터 쌓아서 풀어내니까 2페이지가 3페이지가 되고 3페이지가 1페이지가 되고... 이런 일은 일어날 수가 없었던 것이었다.


ㅎ ㅏ... 어쨌든 이해 완...

0개의 댓글