Array.forEach는 await를 기다려주지 않는다. 이 문제를 직면했을때 검색을 통해 forEach 대신 for of문을 사용하면 된다는 해결책은 얻었지만 정작 이때까지 그 이유를 확실하게 알지 못했다.
오늘은 그 이유에 대해 확실하게 알아볼려 한다.
test() {
webtoons.forEach(
async (webtoon) => {
const newWebtoon = await this.webtoonModel.findOne({
where: { title: webtoon.title },
});
console.log(newWebtoon.id);
}
);
}
위와 같이 forEach의 콜백함수에 async/await 함수를 넣었을때 결과는 다음과 같다.
아무런 문제가 없어 보이지만 중요한 문제가 있다.
바로 순서대로 반복해야 하는 반복문에서 순서가 꼬이는 것이다. 위의 결과에서 1072번은 1073번 보다 먼저 실행되었지만 1073번의 결과가 먼저 출력됐다. 그럼 for of의 경우에는 어떨까? 바로 알아보자.
test() {
for (let webtoon of webtoons) {
const newWebtoon = await this.webtoonModel.findOne({
where: { title: webtoon.title },
});
console.log(newWebtoon.id);
}
}
for of문의 await를 적용했을때는 다음과 같다.
for of는 위의 결과와 같이 순서대로 작동한다.
forEach와 for of의 차이점은 await를 기다리는지 아닌지의 차이다. 그렇다면 이것의 원인은 무엇일까??
처음에 당황스러웠던 것은 공식문서에서도 forEach는 await를 기다리지 않는다라고만 써있고 이유를 알려주지 않는 것이였다. 하지만 이유가 너무 간단하기에 써놓지 않은 것이였다.
forEach의 구조는 다음과 같이 되어있다.
Array.prototype.forEach = function (callback, context) {
for (let i = 0; i < this.length; i++) {
callback.call(context || null, this[i], i, this);
}
};
여기서 내가 콜백 함수에 아까와 같은 비동기 함수를 넣으면 다음과 형태가 된다.
Array.prototype.forEach = function (callback, context) {
for (let i = 0; i < this.length; i++) {
(async (webtoon) => {
const newWebtoon = await this.webtoonModel.findOne({
where: { title: webtoon.title },
});
console.log(newWebtoon.id);
})(context || null, this[i], i, this);
}
};
여기서 문제가 생겨버리는 것이였다.. 결국은 반복문에서 비동기로 함수를 실행 해버리니까 결과의 순서가 뒤섞이는 것이었다.
만약 내가 원하는 결과가 나올려면 forEach가 다음과 같은 형태여야 했다.
Array.prototype.forEach = async function (callback, context) {
for (let i = 0; i < this.length; i++) {
await callback.call(context || null, this[i], i, this);
}
};
forEach는 3번의 원인에서 처럼 내부적으로 비동기 함수를 기다리지 않기 때문에 순서가 꼬일수 있다. 즉 forEach와 for of를 사용할때는 다음과 같은 특성을 고려해야 할 것이다.
forEach
1. 비동기 함수가 콜백함수로 오는 경우 반복의 순서가 상관이 없어야 한다.
for of
2. 비동기, 동기 상관없이 무조건 순차적으로 처리한다.