forEach 는 비동기를 기다려주지 않는다?

eeensu·2024년 8월 11일
0

javascript

목록 보기
31/33

forEach

js의 forEach는 배열에서 사용할 수 있는 함수로, 배열의 각 요소를 순회하는 콜백함수를 전달하여 실행시키는 함수이다. forEach에는 다음과 같은 특징이 있다.

  • forEach는 원본 배열을 변경하지 않으며 콜백 함수 내에서 배열을 변경할 수는 있다.
  • forEach는 항상 undefined를 반환한다. 배열의 요소를 변환하거나 필터링하려면 다른 메서드(map, filter 등)를 사용해야 한다.
const array = [1, 2, 3, 4];

array.forEach(function(element, index, array) {
  console.log('Element:', element);
  console.log('Index:', index);
  console.log('Array:', array);
});



비동기를 기다려주지 않는다고?

그리고, 개발을 하던 중, 각 배열의 요소를 순회하며 하나씩의 인자를 가지고 비동기 요청을 해야하는 상황이 생겼다.

// 예시
tasks.forEach(async (task) => {
  await promise(task);
});

각 배열을 순회하며 도는 콜백함수이기에, await 를 걸어두면 당연히 비동기 작업도 각 요소의 순서대로 작업이 이루어질 줄 알았다. 그러나 비동기 작업은 정상적으로 이루어지지 않았다.

조금 더 구글링을 한 결과, mdn에서는 정확히 forEach는 비동기작업을 지원해주지 않는다고 명시해놓았다.

forEach() expects a synchronous function — it does not wait for promises. Make sure you are aware of the implications while using promises (or async functions) as forEach callbacks.

const ratings = [5, 4, 5];
let sum = 0;

const sumFunction = async (a, b) => a + b;

ratings.forEach(async (rating) => {
  sum = await sumFunction(sum, rating);
});

console.log(sum);
// Naively expected output: 14
// Actual output: 0


즉, forEach는 본질적으로 동기 방식으로 작동한다. 이는 각 콜백이 비동기 작업을 포함하더라도, forEach 자체는 이를 기다리지 않고 즉시 다음 요소로 진행함을 의미한다. 결과적으로 비동기 작업의 완료 순서는 예측할 수 없게 된다. 예를 들어

const tasks = [1, 2, 3, 4];

tasks.forEach(async (task) => {
  await new Promise(resolve => setTimeout(resolve, 1000));
  console.log(`Task ${task} done`);
});

위 코드의 실행결과는 아래와 같을 수 있다. 하지만, 실제로는 거의 동시에 실행되며 순서가 섞일 수도 있기에 주의해야 한다.

Task 1 done
Task 2 done
Task 3 done
Task 4 done

// 또는
Task 1 done
Task 3 done
Task 2 done
Task 4 done



대안

그렇다면 비동기작업을 순차적으로 처리하거나 모든 비동기 작업이 완료될 때 까지 기다리려면 forEach 가 아닌 다른 방법을 써야 한다. 어떠한 방법들이 있을까?

1. for of 루프

for ... of 루프는 비동기 작업을 순차적으로 처리하는 것을 보장하며 실행된다.

const tasks = [1, 2, 3, 4];

for (const task of tasks) {
    await new Promise(resolve => setTimeout(resolve, 1000));
    console.log(`Task ${task} done`);
  }
}

2. Promise.all

모든 비동기 작업이 순서 상관없이, 병렬로 실행되길 원한다면 Promise.all 을 사용할 수 있다.

const tasks = [1, 2, 3, 4];

const promises = tasks.map(task => new Promise(resolve => {
    setTimeout(() => {
      console.log(`Task ${task} done`);
      resolve();
    }, 1000);
  }));

await Promise.all(promises);



비동기 작업을 기다려주지 않는 이유?

  1. 비동기 작업의 뜻은, 다른 작업이 완료될 때까지 기다리지 않고 바로 다음 작업을 실행하는 방식이다. 예를 들어, 서버 요청, 파일 읽기, 타이머 등은 비동기 작업이다. 이러한 작업은 나중에 완료되지만, 그 사이에 다른 코드가 계속 실행될 수 있다.

  2. forEach는 동기적으로 작동한다. 즉, 배열의 각 요소에 대해 콜백 함수를 실행한 후, 그 함수가 완료되기를 기다리지 않고 다음 요소로 넘어간다. 비동기 작업을 콜백 함수 내에서 수행하더라도, forEach는 이를 기다리지 않는다.

profile
안녕하세요! 26살 프론트엔드 개발자입니다! (2024/03 ~)

0개의 댓글