프로젝트를 진행하며 배열에 담긴 요소들을 s3에 이미지 업로드를 해야해서 아래와 같이 코드를 작성하였다.
srcList.forEach(async (tag, idx) => {
...
const data = await s3.upload(param).promise();
result = result.replace(srcData, data.Location);
...
});
console.log(result);
이런 식으로 작성하였더니 result 값에 원하던 값이 들어가있지 않고 초기에 넣어준 값이 나왔다.
그 이유는 forEach는 내부에 들어있는 순차적으로 배열을 돌며 callback을 실행을 하기 때문에 callback이 동기인지 비동기인지에 따라 달라지지 않는다고 한다. 따라서 forEach는 비동기를 기다려 주지 않는다.
그래서 위와 같이 작성한 코드는 내부 callback에서도 비동기 처리를 기다려 주지 않고 result를 출력했을 때도 내부의 비동기처리를 기다려주지 않기 때문에 초기값이 출력된다고 한다.
Note : forEach expects a synchronous function.
forEach does not wait for promises. Make sure you are aware of the implications while using promises (or async functions) as forEach callback.
mdn web docs
위와 같이 mdn web docs 의 Array.prototype.forEach()의 설명에 forEach는 비동기 함수를 기다려주지 않는다고 나와있다.
for...of 명령문은 반복가능한 객체 (Array, Map, Set, String, TypedArray, arguments 객체 등을 포함)에 대해서 반복하고 각 개별 속성값에 대해 실행되는 문이 있는 사용자 정의 반복 후크를 호출하는 루프를 생성합니다.
mdn web docs
for...of 명령문은 각 항목에 대해 비동기 처리를 기다리기 때문에 대안이 될 수 있다. 그래서 위의 코드를 아래와 같이 수정하였다.
for (let tag of srcList) {
const data = await s3.upload(param).promise();
result = result.replace(srcData, data.Location);
}
console.log(result);
위와 같이 코드를 작성하니 비동기 처리가 성공적으로 이루어 지기 때문에 result값이 정상적으로 출력이 되었다. 하지만 for...of는 병렬적으로 처리하지 않기 때문에 배열이 길어질 경우 시간이 오래 걸릴 수 있다고 한다. 위의 단점을 해결하기 위한 방법으로는 Promise.all()이 있다.
이 방법은 비동기 처리의 시간이 오래걸릴 경우 순차적으로 하는 것 보다 병렬적으로 처리하도록 한다. 비동기 함수를 Promise 배열로 만든 후 Promise.all()을 사용하여 모든 비동기 함수를 병렬적으로(동시에) 실행할 수 있다.
그래서 아래와 같이 코드를 수정하였다.
const promiseList = srcList.map(async (tag, idx) => {
...
return await s3
.upload(param)
.promise()
.then((data) => {
result = result.replace(srcData, data.Location);
});
});
await Promise.all(promiseList);