자바스크립트 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 무시 | O | X | forEach는 await을 기다리지 않음 |
for...of/for...in | 동기(순차) | await 사용 시 순차 실행 | X | O | 각 반복마다 await 필요 |
for...of + Promise.all | 병렬 | 프로미스 배열 후 await | O | X | 모든 작업 병렬 처리 |
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의 동작 방식 및 예시