자바스크립트 for, forEach, for...of, Promise.all의 비동기 처리 차이

6/13/2025

JavaScript 반복문에서 비동기 처리: forEach, for...of, Promise.all 비교

프론트엔드 개발에서 배열을 반복하며 비동기 작업을 처리할 때, forEach, for...in, for...of, 그리고 Promise.all의 동작 방식과 차이를 명확히 이해하는 것이 중요합니다. 각각의 특징과 병렬/순차 실행 방식, 그리고 실제 코드 예제를 아래에 정리합니다.


1. forEach, for...of, for...in의 비동기 처리 차이

  • forEach

    • forEach는 내부 콜백에서 async/await을 사용해도, 각 반복을 기다리지 않고 모두 한 번에 실행합니다.
    • 즉, 내부의 async 함수는 병렬적으로 실행되지만, forEach 자체는 프로미스의 완료를 기다리지 않습니다.
    • 결과적으로 순차적 실행이 필요할 때는 적합하지 않습니다.
  • for...of / for...in

    • for...of(또는 for...in) 반복문은 async/await과 함께 사용할 수 있으며, 각 반복에서 await을 사용하면 이전 반복이 끝날 때까지 기다립니다.
    • 즉, 순차적으로(동기적으로) 실행됩니다.
    • for...of는 배열, for...in은 객체의 key에 적합합니다.

2. for...of에서 async/await을 통한 병렬 실행

  • for...of에서 기본적으로 await을 사용하면 반복이 순차적으로 실행됩니다.
  • 병렬 실행을 원한다면, 반복문에서 프로미스를 생성해 배열에 담고, 마지막에 Promise.all로 한 번에 처리할 수 있습니다.

예시: 순차 실행 (동기적)

async function sequentialProcess(arr) {
  for (const item of arr) {
    await asyncTask(item);
  }
  console.log('모든 작업 완료');
}
  • 위 코드는 각 asyncTask가 끝날 때까지 다음 반복이 대기합니다.

예시: 병렬 실행 (Promise.all)

async function parallelProcess(arr) {
  const promises = arr.map(item => asyncTask(item));
  await Promise.all(promises);
  console.log('모든 작업 완료');
}
  • 모든 asyncTask가 동시에 시작되고, 가장 오래 걸리는 작업이 끝나면 전체가 완료됩니다.

3. Promise.all을 활용한 병렬 실행

  • Promise.all은 여러 프로미스를 병렬로 실행한 뒤, 모든 작업이 끝날 때까지 기다립니다.
  • 각 작업이 서로 독립적이고, 동시에 처리해도 무방할 때 유용합니다.
  • 단, 하나라도 실패하면 전체가 reject됩니다.

예시

async function asyncTask(item) {
  // 예시: 1초 후 item 반환
  return new Promise(resolve => setTimeout(() => resolve(item), 1000));
}

async function runParallel() {
  const items = [1, 2, 3, 4, 5];
  const results = await Promise.all(items.map(asyncTask));
  console.log(results); // [1, 2, 3, 4, 5]
}

4. 각 방식의 비교

방식실행 방식코드 예시에서의 await 처리병렬 실행순차 실행주의점
forEach비동기(병렬)콜백 내 await 무시OXforEach는 await을 기다리지 않음
for...of/for...in동기(순차)await 사용 시 순차 실행XO각 반복마다 await 필요
for...of + Promise.all병렬프로미스 배열 후 awaitOX모든 작업 병렬 처리

5. 실제 코드 비교

forEach (병렬, 완료 대기 불가)

const arr = [1, 2, 3];
arr.forEach(async (item) => {
  await asyncTask(item);
  console.log(item); // 병렬 실행, 순서 보장 안 됨
});
console.log('끝'); // forEach가 끝나기 전에 실행됨

for...of (순차)

async function run() {
  for (const item of [1, 2, 3]) {
    await asyncTask(item);
    console.log(item); // 1, 2, 3 순서대로 출력
  }
  console.log('끝'); // 모든 작업 후 실행
}
run();

for...of + Promise.all (병렬)

async function run() {
  const arr = [1, 2, 3];
  await Promise.all(arr.map(async (item) => {
    await asyncTask(item);
    console.log(item); // 병렬 실행, 순서 보장 안 됨
  }));
  console.log('끝'); // 모든 작업 후 실행
}
run();

6. 결론 및 활용 팁

  • 순차 실행이 필요하다면 for...of + await 사용.
  • 병렬 실행이 필요하다면 Promise.all과 map 사용.
  • forEach는 비동기 작업을 제대로 기다리지 않으므로, 실제 비동기 반복에는 권장하지 않음.
  • 병렬 실행 시 작업량이 많으면 서버/브라우저에 부하가 갈 수 있으니 주의.

참고

  • forEach와 async/await의 관계
  • for...of/for...in + async/await, Promise.all의 차이
  • Promise.all의 동작 방식 및 예시

© 2025 Mingu Kim. All rights reserved.