[FIFAPulse] 개발기록 - Promise.all로 여러 비동기를 병렬로 처리하기

조민호·2023년 4월 28일
0

이전에 진행했던 프로젝트에서는 비동기 데이터 요청을 할 때,

특정 기능들이 단일 데이터만 필요했었던 경우만 있었지만

이번 프로젝트에서는 여러 데이터를 한번에 받아와야 하는 경우가 잦았다

그러므로 단순하게 각 데이터들을 배열에 push하는 것 보다, Promise.all을 사용해 보았다




Promise.all 이란?

Promise.all에 대해 간단히 설명을 해보자면

여러 개의 프라미스를 동시에 실행시키고 모든 프라미스가 준비될 때까지 기다릴 때 사용하는 프로미스 객체의 빌트인 메소드이다

즉, 여러 개의 비동기 처리를 모두 병렬처리할 때 사용하는 것이다

Promise.all은 요소 전체가 프로미스인 배열을 받고 새로운 프로미스를 반환 한다

배열 안 프라미스가 모두 처리되면 새로운 프라미스가 이행되는데,

배열 안 프라미스의 결괏값을 담은 배열이 새로운 프라미스의 result가 되는 것이다

또한 Axios 버전 0.21.0 이후에는 axios.all() 대신 Promise.all()을 사용하는 것이 권장된다


아래는 Promise.all()을 사용하는 간단한 예시이다
const requestData1 = () =>
  new Promise((resolve) => setTimeout(() => resolve(1), 3000));
const requestData2 = () =>
  new Promise((resolve) => setTimeout(() => resolve(2), 2000));
const requestData3 = () =>
  new Promise((resolve) => setTimeout(() => resolve(3), 1000));

Promise.all([requestData1(), requestData2(), requestData3()])
  .then(console.log) // [ 1, 2, 3 ] ⇒ 약 3초 소요
  .catch(console.error);

Promise.all 메서드는 인수로 전달받은 배열의 모든 프로미스가 fulfilled 상태가 되면 종료한다.

따라서 Promise.all 메서드가 종료하는 데 걸리는 시간은 가장 늦게 fulfilled 상태가 되는 프로미스의 처리 시간보다 조금 더 길다.

위 예시의 경우 3초보다 조금 더 ( 비동기 로직은 테스크큐로 옮겨졌다가 이벤트 루프에 의해 전달되는 과정이 있으므로 ) 소요되는 것이다

이처럼 Promise.all()은 프로미스의 처리 시간에 의해 총 처리 시간이 결정되지만,

순서는 코드의 처리 순서를 보장한다.



map과 함께 사용도 가능하다


  • getActionImg()함수는 비동기 요청을 하는 함수이고

  • arr이라는 배열에 getActionImg()함수의 인자로 필요한 id값들을 가지고 있다


그러므로 arr 배열의 갯수만큼 비동기 처리를 전부 진행해야 하는 상황이라면

아래와 같이 직접 일일이 배열을 작성해서 Promise.all로 감싸는 방법도 있겠지만

// async 함수 내부
const result = await Promise.all([
      getActionImg(arr[0].spid),
      getActionImg(arr[1].spid),
      getActionImg(arr[2].spid),
      getActionImg(arr[3].spid)
    ]);

map을 사용해서 배열을 반환시킨 값에다가 Promise.all로 감싸는 방법도 있다

// async 함수 내부
const result = await Promise.all(
        arr.map((i) => {
          return getActionImg(i.spId);
        }),
);



프로젝트에 Promise.all 사용해보기


현재 내 프로젝트에서는 피파온라인에서 제공하는 api중 ,

메타데이터를(선수 id , 경기등급 , 포지션) 반환 해주는 api들이 있다


그렇지만 이런 메타데이터들이 하나의 메소드로 전부 제공되지 않고

각 메소드별로 분리가 되어 있다

그래서 메타데이터를 가져올 때 , 메타데이터를 가져오는 메소드들을 전부 비동기 호출한뒤

모든 요청이 완료가 되면 그때 병렬로 반환 데이터를 한번에 가져온다


아래 getInfoMetaData()가 그 예시이다

export default class FIFAData {
  instance;

  constructor() {
    this.instance = axios.create({
      baseURL: 'https://api.nexon.co.kr/fifaonline4/v1.0',
      headers: {
        Authorization: import.meta.env.REACT_APP_API_KEY_FIFA,
      },
    });
  }

	...

	// Promise.all 사용
  getInfoMetaData = async () => {
    return Promise.all([this.#getMatchtype(), this.#getSeasonId(), ...]);
  };

  // 시즌아이디
  #getSeasonId = async <T extends MetaDataSeasonId>(): Promise<T[]> => {
    const result = await axios.get<T[]>('https://static.api.nexon.co.kr/fifaonline4/latest/seasonid.json', {
      headers: {
        Authorization: import.meta.env.REACT_APP_API_KEY_FIFA,
      },
    });

    return result.data;
  };

  // 매치 종류
  #getMatchtype = async <T extends MetaDataMatchtype>(): Promise<T[]> => {
    const result = await axios.get<T[]>('https://static.api.nexon.co.kr/fifaonline4/latest/matchtype.json', {
      headers: {
        Authorization: import.meta.env.REACT_APP_API_KEY_FIFA,
      },
    });

    return result.data;
  };

	...

}




또한, 내 기록을 보는 페이지(MyRecord.tsx)에서

  • select box에서 매치종류를 선택하면

  • 해당 매치 종류의 최근 전적 10개에 대한 매치id 를 불러오고

  • 이 10개의 매치id를 이용해서 각 매치의 세부 데이터를 반환해주는 api를 사용해야 했다

즉, 매치id 10개에 대한 매치데이터10개를 호출해서 한번에 받아 오는 것이다




그러므로 이 때 역시 Promise.all을 사용해야 했고 구현 부분은 아래와 같다

useEffect(() => {
    const getMatchDetail = async () => {
      if (matchId.length) {
        const fifa = new FIFAData();
        const matchDetailArr: Promise<MatchDetail>[] = matchId.map((i) => {
          return fifa.getMatchDetail(i);
        });
        const matchDetailsPromises: MatchDetail[] = await Promise.all(matchDetailArr);
        setMatchDetail(matchDetailsPromises);
      }
      if (!matchId.length) {
        setMatchDetail([]);
      }
    };
    getMatchDetail();
  }, [matchId]);
  • matchId가 존재하면 matchId를 순회하며 api (getMatchDetail) 를 호출하며 배열을 반환한다

  • getMatchDetail()의 반환 타입은 Promise이므로

    matchDetailArr의 타입은 Promise[] 로 지정한다

  • 이때 map 내부에서 await을 사용하지 않는다

    const matchDetailArr: Promise<MatchDetail>[] = matchId.map((i) => {
              return await fifa.getMatchDetail(i); // x 이렇게 사용할 수 없음
    });

    await는 async로 지정한 비동기 함수 내부 및 모듈의 최상위 수준에서만 사용할 수 있기 때문이다

  • 또한 matchDetailArr가 생성이 되면 요소 전체가 (fulfilled상태인)프로미스인 배열이 되는데

    Promise.all()의 인자로 matchDetailArr 같은 요소 전체가 프로미스인 배열을 넣어줘야 하기 때문이기도 하다

  • 그러므로 Promise.all()에 matchDetailArr를 넣어주고 await으로 최종 병렬처리 된 데이터 배열을 받아오는 것이다

profile
웰시코기발바닥

0개의 댓글