카테고리 없음

[포켓몬 맞추기 게임] 데이터 패칭 리팩토링

순코딩 2024. 10. 19. 22:15
  // 리팩토링 전
   
  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();
  }, []);

 

주요 변경 사항:

  1. 병렬 요청:
    • Promise.all을 사용하여 모든 axios.get 요청을 병렬로 처리함으로써 API 요청 속도를 개선했습니다.
    • 이제 각 포켓몬에 대해 개별적으로 데이터를 가져오는 것이 아닌, 동시에 여러 포켓몬 데이터를 가져옵니다.
  2. 데이터 구조 정리:
    • 각 포켓몬의 데이터를 추출할 때 한국어 이름이 없을 경우 기본 영어 이름을 사용하는 로직을 추가했습니다.
    • 데이터를 설정할 때 바로 상태에 저장하지 않고, 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 요청을 더 빠르게 완료할 수 있습니다. 여러 개의 네트워크 요청을 동시에 처리하여 대기 시간을 단축시키는 것이 장점입니다.
- 직렬로 처리하는 경우 각 요청이 완료될 때까지 대기해야 하므로 시간이 많이 소요되지만, 병렬 처리를 통해 전체 시간을 크게 줄일 수 있습니다.

이 방식으로 성능을 크게 개선할 수 있습니다!