본문 바로가기

자바스크립트

자바스크립트) forEach와 비동기

 

최근 코드를 작성하는 중에 한번에 여러개의 비동기 작업을 처리해야 할 일이 생겼다.

단순히 async awiat을 사용해 다음과 같이 구현할 수 있지만,

const data = await fetch("/api");
const data2 = await fetch("/api");
const data3 = await fetch("/api");
const data4 = await fetch("/api");

특정 변수를 배열에 담고 그 배열을 주소값 또는 파라미터로 넣어 api를 여러번 호출해야 하는 상황이었다.

    const number_arr = [1, 2, 3, 4, 5];

    const data = await fetch(`/api/${number_arr[0]}`);
    const data2 = await fetch(`/api/${number_arr[1]}`);
    const data3 = await fetch(`/api/${number_arr[2]}`);
    const data4 = await fetch(`/api/${number_arr[3]}`);

때문에 아무런 의심없이 forEach를 활용해 비동기 작업을 처리했었다.

실제 더미api를 불러오는 코드로 새로 작성했다.

    const number_arr = [1, 2, 3, 4, 5];

    const response_arr: any = [];

    number_arr.forEach(async (num) => {
      const { data }: AxiosResponse = await axios.get(
        `https://jsonplaceholder.typicode.com/todos/${num}`
      );
      response_arr.push(data.id);
    });

반복문을 사용해 총 5번의 api를 호출했고 그 값중에 id값을 response_arr에 넣는다.

예상으로는 data의 id 값들인 1,2,3,4,5가 response_arr안에 담겨야 한다.

그러나 빈 값이 출력된다.

 

왜 그런 걸까?

제목에서부터 예상했듯이 forEach는 비동기함수를 지원하지 않는다. 더 정확하게는 기다리지 않는다는 말이 맞다.

즉 반복문을 돌며 짜여진 로직만 실행할 뿐, callback등의 작업이 끝날때까지 기다리지 않고 다음으로 넘어가기 때문이다.

쉽게말해 forEach문은 비동기를 실행하든 안하든 x도 신경안쓴다는 뜻이다.

위 코드에서는 api를 호출하고 response_arr에 담기 전에 다음 api를 호출하고 그 값을 response_arr에 담기전에 다음 api를 호출하고...... 결국 response_arr에 어떤 값을 담기 전에 반복문이 끝나게 되고 cosnole.log()가 실행되며 빈 값이 출력되는 것이다.

 

어떻게 해결할까?

 

방법은 크게 두가지가 존재한다.

하나는 for of문을 사용하는 것이다.

아래는 위 forEach문을 for of로 바꾼 코드이다.

    for (let num of number_arr) {
      const { data }: AxiosResponse = await axios.get(
        `https://jsonplaceholder.typicode.com/todos/${num}`
      );

      response_arr.push(data.id);
    }

    console.log(response_arr);

 

또는 병렬처리를 원할 경우 map함수를 사용할 수가 있다.

다만 map을 사용할 경우 Promise.all을 사용해 모든 프로미스의 결과를 기다릴 필요가 있다.

    const promise_arr = number_arr.map((num) => {
      return axios
        .get(`https://jsonplaceholder.typicode.com/todos/${num}`)
        .then((response) => response.data.id);
    });

    Promise.all(promise_arr)
      .then((response_arr) => {
        console.log(response_arr);
      })
      .catch((error) => {
        console.log(error);
      });

 

 

궁금한 점

해당 포스트를 작성하는 중 궁금한 점이 생겨났다.

forEach는 비동기작업을 기다리지 않는데, react-query의 useMutation은 병렬작업을 위해 forEach를 사용하는데요?

 

다음과 같은 코드가 있다.

 ["/insert_item_log", "/insert_clan_bank"].forEach((addr) => {
        mutate(
          {
           id:addr
          },
          {
            onSuccess,
          }
        );
      });

react-query의 useMutation을 사용하기 위한 mutate함수의 코드이다.

분명 forEach는 비동기작업을 기다리지 않는다는데 forEach를 사용해도 무방했다.

왜 그럴까.

 

'forEach' 메서드를 사용하는 경우에도 'mutate'함수는 비동기 함수지만 React Query의 내부 구현은 비동기 작업을 처리할 수 있도록 설계되어 있다.

React-Query는 각각의 'mutate'호출을 병렬로 처리하고, 각각의 비동기 작업이 완료될 때마다 상태를 업데이트한다.

 

따라서, 위 코드에서 'forEach'를 사용하여 'mutate'를 호출하는 경우, 각각의 비동기 작업이 동시에 실행되고 완료될 때마다 'onSucces'콜백이 실행된다.

이는 React-Query가 내부적으로 병렬 처리를 지원하기 때문이다.

React-Query는 내부적으로 프로미스 관리와 상태 업데이트를 처리하기 때문에 별도의 비동기 작업 처리 방식을 고려할 필요가 없다.

'forEach'를 사용하여 여러개의 'mutate'를 호출하더라도 React-Query는 각각의 비동기 작업을 병렬로 처리하고 상태를 업데이트하여 정확한 결과를 반환한다.