[포켓몬 맞추기 게임] 데이터 패칭 리팩토링
// 리팩토링 전
useEffect(() => {
const fetchData = async () => {
const allPokemonData = [];
for (let i = 1; i <= 151; i++) {
const response = await axios.get(`https://pokeapi.co/api/v2/pokemon/${i}`);
const speciesResponse = await axios.get(`https://pokeapi.co/api/v2/pokemon-species/${i}`);
const koreanName = speciesResponse.data.names.find((name) => name.language.name === "ko");
allPokemonData.push({ ...response.data, korean_name: koreanName.name });
}
const needData = lodash.shuffle(allPokemonData).map((el) => {
return {
src: el.sprites.front_default,
name: el.korean_name,
};
});
setPokemonData(needData);
};
fetchData();
}, []);
// 리팩토링 후
const fetchData = async () => {
try {
// 151개 포켓몬의 정보를 병렬로 모두 가져오기
const pokemonRequests = Array.from({ length: 151 }, (_, i) =>
axios.get(`https://pokeapi.co/api/v2/pokemon/${i + 1}`)
);
const speciesRequests = Array.from({ length: 151 }, (_, i) =>
axios.get(`https://pokeapi.co/api/v2/pokemon-species/${i + 1}`)
);
// 모든 요청이 완료될 때까지 기다림 (병렬로 처리됨)
const pokemonResponses = await Promise.all(pokemonRequests);
const speciesResponses = await Promise.all(speciesRequests);
// 데이터를 정리하여 필요한 정보를 추출
const allPokemonData = pokemonResponses.map((response, index) => {
const speciesResponse = speciesResponses[index].data;
const koreanName = speciesResponse.names.find((name) => name.language.name === "ko");
return {
src: response.data.sprites.front_default,
name: koreanName ? koreanName.name : response.data.name, // 한국 이름이 없을 경우 기본 이름 사용
};
});
// 데이터 섞기 (shuffle) 및 필요한 형태로 변환
const needData = lodash.shuffle(allPokemonData);
return needData;
} catch (error) {
console.error("Error fetching Pokémon data:", error);
return [];
}
};
useEffect(() => {
const fetchAndSetData = async () => {
const data = await fetchData();
setPokemonData(data);
};
fetchAndSetData();
}, []);
주요 변경 사항:
- 병렬 요청:
- Promise.all을 사용하여 모든 axios.get 요청을 병렬로 처리함으로써 API 요청 속도를 개선했습니다.
- 이제 각 포켓몬에 대해 개별적으로 데이터를 가져오는 것이 아닌, 동시에 여러 포켓몬 데이터를 가져옵니다.
- 데이터 구조 정리:
- 각 포켓몬의 데이터를 추출할 때 한국어 이름이 없을 경우 기본 영어 이름을 사용하는 로직을 추가했습니다.
- 데이터를 설정할 때 바로 상태에 저장하지 않고, fetchAndSetData 함수로 데이터를 수집 후 처리하여 상태 업데이트.
`Promise.all`을 사용하면 **비동기 작업을 병렬로 처리**할 수 있어서 성능이 크게 개선됩니다. 이를 이해하기 위해, `Promise.all`의 개념과 기존 `for` 루프를 사용한 방법의 차이를 설명하겠습니다.
### 1. **기존 방식 (직렬 처리)**:
- `for` 루프와 `await`를 사용한 방식에서는 **하나의 요청이 완료된 후에** 다음 요청이 시작됩니다. 즉, 151개의 포켓몬 데이터를 가져올 때, 각 요청이 순차적으로 실행됩니다. 이렇게 하면 모든 요청이 끝날 때까지 **총 시간이 더 길어지게 됩니다.**
**예시:**
```javascript
for (let i = 1; i <= 151; i++) {
const response = await axios.get(`https://pokeapi.co/api/v2/pokemon/${i}`);
// 다음 요청이 이전 요청이 완료될 때까지 기다린다.
}
```
- **단점**: 요청이 순차적으로 처리되므로 151개의 요청이 끝날 때까지 시간이 오래 걸립니다. 네트워크 대기 시간이 누적되기 때문입니다.
### 2. **`Promise.all` 사용 방식 (병렬 처리)**:
- `Promise.all`을 사용하면 모든 API 요청이 **동시에** 시작됩니다. 즉, **한 번에 여러 개의 요청이 병렬로 실행**되므로 전체 작업이 더 빨리 완료됩니다.
- `Promise.all`은 **모든 비동기 작업이 완료될 때까지 기다렸다가** 결과를 반환합니다.
**예시:**
```javascript
const promises = [];
for (let i = 1; i <= 151; i++) {
promises.push(axios.get(`https://pokeapi.co/api/v2/pokemon/${i}`));
}
const responses = await Promise.all(promises);
```
이 방식에서는 모든 요청이 병렬로 실행되므로 **각 요청이 서로 기다리지 않습니다**. 따라서 네트워크 요청이 끝나면 모든 요청 결과를 한꺼번에 받을 수 있습니다.
### **비교:**
| 방식 | 처리 방식 | 장점 | 단점 |
| ---------------- | --------------------- | ---------------------------------------- | -------------------------------- |
| **직렬 처리** | 요청이 순차적으로 실행 | 코드가 직관적일 수 있음 | 각 요청이 완료될 때까지 대기해야 해서 시간이 많이 걸림 |
| **병렬 처리 (`Promise.all`)** | 요청이 동시에 실행 | 모든 요청을 동시에 처리하여 처리 속도 향상 | 네트워크 부하가 순간적으로 증가할 수 있음 |
### **예시 시간 비교**:
- **직렬 처리**: 각 요청에 100ms가 걸린다고 가정하면, 151개의 요청을 완료하는 데 151 * 100ms = 15.1초가 소요됩니다.
- **병렬 처리 (`Promise.all`)**: 151개의 요청이 모두 동시에 시작되면, 가장 오래 걸리는 요청이 끝나는 시간만큼 걸립니다. 즉, 100ms가 걸리는 요청이 가장 늦게 끝난다고 가정하면 전체 시간이 100ms가 됩니다. **결과적으로 15.1초에서 0.1초로 시간이 크게 줄어듭니다.**
### **`Promise.all` 사용 시 유의 사항**:
1. **네트워크 부하**: 모든 요청이 동시에 이루어지므로 네트워크 부하가 순간적으로 커질 수 있습니다. 하지만 대부분의 API 서버는 이러한 병렬 요청을 처리할 수 있는 성능을 가지고 있습니다.
2. **오류 처리**: `Promise.all`은 **하나의 요청이라도 실패하면 전체가 실패**로 처리됩니다. 이를 방지하려면 각각의 요청을 개별적으로 처리하거나, `try-catch`를 통해 오류를 관리해야 합니다.
### 최적화된 코드 예시:
```javascript
const fetchData = async () => {
try {
const promises = Array.from({ length: 151 }, (_, i) =>
axios.get(`https://pokeapi.co/api/v2/pokemon/${i + 1}`)
);
const responses = await Promise.all(promises);
// 데이터를 정리하고 필요하면 추가 작업을 합니다.
const allPokemonData = responses.map(response => response.data);
// 결과를 사용하여 필요한 데이터만 추출
setPokemonData(allPokemonData);
} catch (error) {
console.error("Error fetching Pokémon data:", error);
}
};
```
### **결론**:
- `Promise.all`을 사용하면 **병렬 처리**가 가능해져서 API 요청을 더 빠르게 완료할 수 있습니다. 여러 개의 네트워크 요청을 동시에 처리하여 대기 시간을 단축시키는 것이 장점입니다.
- 직렬로 처리하는 경우 각 요청이 완료될 때까지 대기해야 하므로 시간이 많이 소요되지만, 병렬 처리를 통해 전체 시간을 크게 줄일 수 있습니다.
이 방식으로 성능을 크게 개선할 수 있습니다!