# 병렬 실행이 await을 기다려주지 않는 이유

## 정리

for 문과 for .. of 문은 순차 함수이기에 await 비동기 함수를 순차적으로 실행할 수 있습니다.

## forEach 문에서는 왜 await 비동기 함수를 기다려주지 않을까?

```javascript
async function run() {
  const items = [1, 2, 3];
  items.forEach(async (item) => {
    await processItem(item);
  });

  console.log("완료");
}
```

위 코드는 processItem 이라는 함수가 끝나기 전에 "완료"를 출력할 수 있습니다. 왜냐하면 forEach문은 아래와 같이 동작할 수 있기 때문입니다.

```javascript
function forEach(callback) {
  for (let i=0; i<array.length; i++) {
    callback(array[i]);
  }
}

callback(1); // Promise 반환, 무시
callback(2); // Promise 반환, 무시
callback(3); // Promise 반환, 무시
```

forEach 문은 콜백이 반환한 Promise를 기다리지 않습니다. 콜백 내부 상황에서 보았을 때 await 자체는 정상 작동하지만, 바깥의 forEach 문에서는 그 Promise를 수집하거나 기다리지 않기 때문에 전체 작업 완료를 추적할 수 없습니다. 정리하면 각 콜백 내부에서는 await이 적용되기는 하지만, 바깥 함수도 forEach의 완료만을 확인할 뿐 비동기 작업의 완료는 확인하지 못한다는 말입니다.

## map도 await를 자동으로 기다려주지 않는다.

```javascript
const results = items.map(async (item) => {
  return await processItem(item);
});
```

위 코드에서 results 실제 결과 배열이 아니며, Promise[] 형태로 리턴이 됩니다. map 역시 콜백의 Promise를 기다리지 않고, 그대로 배열에 담아 리턴을 해주기 때문에 모든 작업을 병렬로 실행하고 기다리려면 Promise.all로 래핑해주며 사용해야합니다.

```javascript
const results = await Promise.all(
  items.map(async (item) => {
    return await processItem(item);
  });
);

processItem(1); // 시작
processItem(2); // 시작
processItem(3); // 시작

// 모두 완료될 때까지 대기
// 최종 results 반환
```

## 왜 이런 방식으로 동작이 되는지, "순차 실행"과 "병렬 실행"의 관점에서 이해해보자.

다음은 순차 실행코드에 대한 예제입니다. 순차 실행 코드는 하나의 작업이 끝난 뒤 다음 작업을 시작하는 방식입니다.

```javascript
// 상위에 await이 정의된 상태라고 가정합니다.
for (const item of items) {
  await processItem(item);
}
```

각 작업이 1초씩 걸린다고 가정했을 때, 총 3번의 작업이 이뤄진다면 총 3초가 걸리게 될 것입니다.

이 방식이 필요한 경우에는 아래와 같습니다.

- 앞 작업의 결과가 다음 작업에 필요할 때

- API 호출 순서를 보장해야할 때

- 서버 요청 제한 때문에 한 번에 하나씩 호출해야할 때

- DB 작업을 순차적으로 처리해야할 때

다음으로 아래는 병렬 실행 코드에 대한 에제이다. 병렬 실행 코드는 모든 작업이 거의 동시에 시작됩니다.

```javascript
await Promise.all(
  items.map((item) => processItem(item));
);
```

각 작업이 1초씩 걸린다고 가정했을 때 총 3번의 작업이 이뤄진다면 모든 작업이 동시에 수행되기 때문에 총 1초가 걸리게 됩니다.

이 방식이 필요한 경우에는 아래와 같습니다.

- 각 작업이 서로 독립적일 때

- 결과 순서만 배열 순서대로 받으면 될 때

- 동시에 요청해도 문제가 없을 때

await을 기다려주는 반복문의 경우에는 순차 실행 코드의 양상이 대부분이었고, await을 기다려주지 않는 경우에는 병렬 실행 코드의 양상이 대부분입니다. 이러한 차이점이 await을 기다려주고, 안기다려주고의 크나큰 특징이 될 수 있습니다.

## await은 반복문을 멈추는 것이 아닌, 현재 async 함수를 중단합니다.

가장 크게 헷갈리는 부분은 await은 자바스크립트 전체 코드를 멈춘다는 것입니다. await은 자바스크립트 전체 코드를 머추는 것이 아니라 현재 실행되고 있는 함수를 잠시 중단시키는 것입니다.

```javascript
async function run() {
  for (const item of items) {
    await processItem(item);
  }
}
```

다음 예시에서 run() 함수 안에 await이 존재하는데, await을 만나고나면 run() 함수는 잠시 중단되고 다른 코드가 실행되며 자연스럽게 어우러지는 것을 확인할 수 있습니다.

```javascript
async function run() {
  console.log("반복 시작");

  for (const item of [1, 2, 3]) {
    await delay(1000);
    console.log(item);
  }

  console.log("반복 종료");
}

run();
console.log("다른 코드");

// 반복 시작
// 다른 코드
// 1
// 2
// 3
// 반복 종료
```

자바스크립트 스레드 자체가 1초 동안 멈추는 것이 아니라, run() 함수의 나머지 실행만 Promise 완료 이후로 연기되는 것을 확인할 수 있습니다.

## 반복 방식 별 한눈에 확인해보기

| 방식 | await 사용 가능 여부 | 전체 완료 대기 | 실행 방식 |
| --- | --- | --- | --- |
| for | 가능 | 가능 | 가능 |
| for .. of | 가능 | 가능 | 순차 |
| while | 가능 | 가능 | 순차 |
| do .. while | 가능 | 가능 | 순차 |
| forEach(async () ⇒ {}) | 콜백 내부에서는 가능 | 불가능 | 사실상 동시 실행 |
| map(async () ⇒ {}) | 콜백 내부에서는 가능 | 배열만으로는 불가능 | Promise 배열 생성 |
| Promise.all(map(...)) | 가능 | 가능 | 병렬 |
| for await .. of | 가능 | 가능 | 비동기 이터러블 순회 |

For the site tree, see the [root Markdown](https://slashpage.com/timmy.md).
