[TS][NESTJS] 백엔드에서 비동기를 잘 활용해야하는 사례(.then()을 지양하고 await를 지향하는 이유)

여리·2025년 3월 17일
0

데이터가 많아져서 .then()을 사용하게 되는 순간 비동기의 위험성이 존재한다.
이때는 Promise를 통해 전체 비동기화를 진행해야한다.

최근에 외주 프로젝트를 진행하면서 일어난 버그와 문제에 대해서 기록으로 남겨본다.

//문제의 코드 영역

const payload: UserPayload = {
      ...(await this.userRepository
        .findOne({
          select: this.userSelectInfoList,
          where: {
            userId: authTokenDto.loginId,
            userUuid: authTokenDto.uuid,
          },
        })
        .then(async (res) => {
          if (res) {
            const queryRunner = await this.datasource.createQueryRunner()
            await queryRunner.startTransaction()

            try {
              const lastestLoginInfo = await queryRunner.manager.findOne(
                User,
                {
                  where: {
                    userId: authTokenDto.loginId,
                    userUuid: authTokenDto.uuid,
                  },
                },
              )
              
              .
              .
              .

📌 사건의 발단

  • 서비스 프로젝트의 고도화를 제안받고 프로젝트를 진행
  • 기존의 로그인 api에 대해서는 굳이 더 건들고자 하는 이슈가 없었음
    - 해당 로그인은 OAuth(open Authorization)를 사용하여 서비스를 사용할 수 있도록 설계됨.
  • 게다가 내가 작성한 코드가 아닌부분에서 그동안 이슈가 없었기 때문에 오히려 이슈가 발생할 수 있을까봐 굳이 잘 작동하는 api에 대해서 손댈 필요가 없었음.
  • 고도화 과정에서 비정형적으로 로그인 오류가 발생.(network 오류에서는 CORS에러 발생.)
  • 이슈를 해결하기 위해서 여러 방법을 찾아보고 있었고 DB에서 sql로는 데이터가 조회됐지만 코드를 통해서 api가 작동될때는 then을 사용한 영역에서 res가 undefined가 발생하여 서비스 로그인이 되지않아 서비스를 이용할 수 없는 크리티컬한 문제가 발생한 것을 알 수 있었다.

📌 문제의 원인

  • DB에서 SQL로 조회하면 값이 나오는데, then 사용 시 res가 undefined가 되는 이유?
    - then()이 비동기적으로 실행되면서 Promise를 정상적으로 반환하지 못한 경우
    - 네트워크 지연이나 데이터 양이 많아 응답이 지연된 경우
    - 데이터가 너무 커서 then() 내부에서 처리되지 않고 Promise 체인이 끊긴 경우
  • then() 내에서 await를 사용하면 then()이 즉시 결과를 반환하여 await가 완료되지 않았는데, then()이 먼저 실행을 종료해버릴 수 있다. 그렇게 되면 then()이 res에 대해서 undefined를 반환할 가능성이 높아진다.(then을 사용하면 Promise체인이 복잡해지고, 예외처리가 어려워지며 비동기 흐름의 제이가 어렵다.)

📌 해결 방법

  1. then을 사용하지 않고 await 사용
    • then을 사용하면 콜백 체인이 길어지고, 데이터가 많을때에는 제대로 return되지 않는 경우가 발생할 수 있다.
  2. then을 사용하고자 한다면 반드시 Promise를 리턴해야 비동기 흐름이 올바르게 동작하도록 해야한다.
//개선한 코드 영역
//then절을 사용하지 않고 await를 사용하여 비동기화
const payload: UserPayload = await this.userRepository.findOne({
      select: this.userSelectInfoList,
      where: {
        userId: authTokenDto.loginId,
        userUuid: authTokenDto.uuid,
      },
    })
    if (!payload) {
      await this.datasource
        .createQueryBuilder()
        .insert()
        .into(User)
        .values([
          {
            userId: authTokenDto.loginId,
            userUuid: authTokenDto.uuid,
            userSchoolCode: authTokenDto.scCode,
            userSchoolName: authTokenDto.scName,
            isTermOn: isNewMember ? true : false,
          },
        ])
        .execute()

      isFirstVisit = true
    } else {
      if (payload.userUuid !== authTokenDto.uuid) {
        throw new BadRequestException('diff uuid')
      }

      const queryRunner = this.datasource.createQueryRunner()
      await queryRunner.startTransaction()

✅ 추가 참고내용

  • then을 사용하는부분은 콜백지옥을 맛볼 수 있기 때문에 지양해야 하는 부분이기도 하다. 특히 백엔드에서는 특수한 상황을 제외하고는 단적인 활동에 대해서는 콜백에 대해 많은 경우의 수를 작성하게되면 코드의 가독성도 떨어지고 데이터를 제대로 찾을 수 없게 된다.
🚀 then()을 사용해야 하는 경우

❓ then()을 언제 사용해야 할까?
	1.	Promise 체이닝을 사용해야 하는 경우
		• 여러 개의 비동기 요청을 순차적으로 실행해야 할 때
		• await을 사용할 수 없는 문맥 (예: .map() 내부)
	2.	콜백 스타일 코드와 호환해야 할 경우
		• 예전 코드에서 Promise 기반으로 작성되어 있을 때
	3.	즉시 실행할 필요가 없고, 이후 체인에서 데이터를 사용해야 할 경우
    
    - then()을 사용할 때는 반드시 return을 명확히 해야 함.
	- catch()에서 예외 처리를 해야 예기치 않은 undefined 반환을 방지할 수 있음.
	- await을 사용할 수 있으면 가급적 await을 사용하는 것이 더 안전함.
profile
beckend developer

0개의 댓글