이전에 진행했던 프로젝트에서는 비동기 데이터 요청을 할 때,
특정 기능들이 단일 데이터만 필요했었던 경우만 있었지만
이번 프로젝트에서는 여러 데이터를 한번에 받아와야 하는 경우가 잦았다
그러므로 단순하게 각 데이터들을 배열에 push하는 것 보다, Promise.all을 사용해 보았다
Promise.all에 대해 간단히 설명을 해보자면
여러 개의 프라미스를 동시에 실행시키고 모든 프라미스가 준비될 때까지 기다릴 때 사용하는 프로미스 객체의 빌트인 메소드이다
즉, 여러 개의 비동기 처리를 모두 병렬처리할 때 사용하는 것이다
배열 안 프라미스가 모두 처리되면 새로운 프라미스가 이행되는데,
배열 안 프라미스의 결괏값을 담은 배열이 새로운 프라미스의 result가 되는 것이다
또한 Axios 버전 0.21.0 이후에는 axios.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()은 프로미스의 처리 시간에 의해 총 처리 시간이 결정되지만,
순서는 코드의 처리 순서를 보장한다.
getActionImg()함수는 비동기 요청을 하는 함수이고
arr이라는 배열에 getActionImg()함수의 인자로 필요한 id값들을 가지고 있다
아래와 같이 직접 일일이 배열을 작성해서 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);
}),
);
현재 내 프로젝트에서는 피파온라인에서 제공하는 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으로 최종 병렬처리 된 데이터 배열을 받아오는 것이다