본문 바로가기
TIL

TIL 240618 - 트러블슈팅 정리 - 반복문과 비동기 실행 / 비동기 함수를 실행할 때 forEach를 쓰면 안되는 이유

by lemonpie611 2024. 6. 18.

1. 문제 상황

코드는 다음과 같음.

let totalPrice = 0;
cartItems.forEach(async (item) => {
  const menu = await this.menuRepository.getMenuById(item.menuId);
  totalPrice += item.quantity * menu.price;
  if (!menu) {
    throw new BadRequestError('해당 메뉴가 존재하지 않습니다.');
  }
});

const user = await this.userRepository.findById(userId);
const balance = user.points - totalPrice;
if (balance < 0) {
  throw new BadRequestError('잔액부족');
}

const order = await this.orderRepository.createOrder(
  userId,
  totalPrice,
  restaurant.ownerId,
  restaurant.restaurantId,
  cartItems,
  cart.cartId,
);

return order;

 

실제 코드는 좀 길고 복잡한데, 간단하게 설명하면 forEach문에서 cartItems의 price와 quantity를 곱한 값을 totalPrice에 더하는 연산을 진행하고, user.points에서 totalPrice를 뺀 잔액을 검사한 뒤 db에 저장하는 service 코드이다.

 

문제는, 작동은 되지만, 여기서 totalPrice가 적용이 됐다 안됐다 한다는것. 아예 안되는 것도 아니고, 정말 랜덤하게 될때도 있고 안될때도 있었다.

 

2. 문제 원인 및 해결과정

console.log을 여러번 찍어보면서 service계층의 forEach문에서 문제가 발생한것을 알 수 있었다.

원래대로면, forEach문을 먼저 실행한 후 잔액 비교와 데이터 저장 과정이 일어나야 하는데, forEach문의 경우 비동기를 호출할 수는 있지만 비동기함수가 완료되기까지 기다리지는 않는다. 따라서, forEach 내부 함수가 모두 실행 완료되기도 전에, 잔액 비교와 데이터 저장을 실행해버리는 문제가 발생한다.

 

따라서 forEach 대신 for of 문으로 코드를 수정하였다.

let totalPrice = 0;
for (const item of cartItems) {
  const menu = await this.menuRepository.getMenuById(item.menuId);

  if (!menu) {
    throw new BadRequestError(MESSAGES.ORDER.CREATE.MENU_NOT_FOUND);
  }
  totalPrice += item.quantity * menu.price;
}

const user = await this.userRepository.findById(userId);
const balance = user.points - totalPrice;
if (balance < 0) {
  throw new BadRequestError(MESSAGES.ORDER.CREATE.INSUFFICIENT);
}

const order = await this.orderRepository.createOrder(
  userId,
  totalPrice,
  restaurant.ownerId,
  restaurant.restaurantId,
  cartItems,
  cart.cartId,
);

return order;