return await VS no return await

Dongwon Ahn·2024년 1월 29일
3

JS & TS 학습

목록 보기
3/7
post-thumbnail

회사에서 동료의 코드를 리뷰 진행하다가 DB 조회하는 비동기 함수를 await 없이 return 하는 부분이 있어, 동료와 해당 부분에 대해 이야기를 했습니다.
기존에 return awaitno return await 둘 다 사용이 가능하지만, 각각의 장점에 대해 정확히 알지 못했다고 생각이 들어 따로 파악한 내용을 정리한 글 입니다.


어떻게 사용하는가?

return await와 no return await의 예시입니다.

// 비동기 함수
const simulateAsyncOperation = () => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("결과값");
    }, 1000);
  });
};

// return await 사용
const withReturnAwait = async () => {
  return await simulateAsyncOperation();
};

// no return await 사용
// 비동기 함수이지만, await 없이 return 하여도 정상적으로 호출됨 
const withReturnOnly = async () => {
  return simulateAsyncOperation();
};

각각의 장점은 무엇인가?

학습한 자료에 의하면 각각의 장점은 아래와 같습니다.

no return await

  • 성능 최적화: return await를 사용하는 경우, 함수가 Promise가 해결되기를 기다린 후 결과값을 반환 합니다. 그 에 반해 return만 사용하는 no return await 같은 경우 즉시 Promise를 반환하고, 호출자가 결과를 기다립니다.
    함수 호출 스택에 불필요한 대기가 줄어들어 성능이 약간 향상 됩니다.
  • 간결성과 명확성: return await를 생략하면 코드가 더 간결해지고 읽기 쉬워집니다.
  • stack trace 간소화: return await를 사용하지 않으면 오류 발생 시 stack trace가 더 간단해집니다.

return await

  • 에러 핸들링: 함수 내부에서 오류를 처리하기 위해 try-catch 블록을 사용하는 경우 Promise를 명시적으로 기다려야 catch 블록이 정상적으로 작동합니다.
  • 비동기 함수의 실행 순서 제어: 특정한 순서로 비동기 작업을 실행해야 하는 경우 await를 사용하여 명시적으로 비동기 작업의 완료를 기다리는 것이 필요합니다.

위 장점 중 간결성은 공감을 하지만, 명확성 같은 경우는 조직, 서비스, 구성원 별로 달라질 수 있을 것 같습니다.
그 외 공감은 되지만, 정확한 결과를 봤으면 하는 부분이 있어 테스트를 진행해봤습니다.

얼마나 성능 최적화가 진행되는가?

위 예시 코드를 통해 테스트를 진행한 결과 값입니다.

  • Return await: 1002.3281669616699 ms
  • Return only: 1001.9307498931885 ms

테스트를 진행한 코드는 아래와 같습니다.

const simulateAsyncOperation = () => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("결과값");
    }, 1000);
  });
};

// return await 사용
const withReturnAwait = async () => {
  return await simulateAsyncOperation();
};

// return만 사용
const withReturnOnly = async () => {
  return simulateAsyncOperation();
};

const measurePerformance = async () => {
  const startWithReturnAwait = performance.now();
  await withReturnAwait();
  const endWithReturnAwait = performance.now();
  console.log(`Return await: ${endWithReturnAwait - startWithReturnAwait} ms`);

  const startWithReturnOnly = performance.now();
  await withReturnOnly();
  const endWithReturnOnly = performance.now();
  console.log(`Return only: ${endWithReturnOnly - startWithReturnOnly} ms`);
};

measurePerformance();

테스트 결과 0.4ms 정도 성능 최적화가 된 것을 확인 할 수 있습니다.

에러 추척

에러 추척이 어려운 이유를 위 예제 코드를 좀 변경해서 테스트 해보겠습니다.
우선 1초 기다리던 함수를 에러를 발생하게 변경하겠습니다.

const simulateAsyncError = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(new Error("시뮬레이션된 에러"));
    }, 1000);
  });
};

no return await가 catch문에 안잡히는 지 확인하기 위해 try catch를 추가하겠습니다.

// return await 사용
const withReturnAwait = async () => {
  try {
    return await simulateAsyncError();
  } catch (error) {
    console.error("withReturnAwait 에러:", error);
    throw error;
  }
};

// return만 사용
const withReturnOnly = async () => {
  try {
    return simulateAsyncError();
  } catch (error) {
    console.error("withReturnOnly 에러:", error);
    throw error;
  }
};

위 함수를 각각 호출하겠습니다.

const testStackTrace = async () => {
  try {
    await withReturnAwait();
  } catch (error) {
    console.error("withReturnAwait 호출 시 에러:", error);
  }

  try {
    await withReturnOnly();
  } catch (error) {
    console.error("withReturnOnly 호출 시 에러:", error);
  }
};

testStackTrace();

결과 값은 아래 이미지와 같이 withReturnOnly함수 같은 경우 withReturnAwait와 다르게 해당 함수에서 catch에 걸리지 않는 것을 확인 할 수 있습니다.

개인적인 결론

저는 성능이 엄청나게 중요한 프로젝트가 아니면, return await를 사용할 것 같습니다.
그렇게 생각한 이유는 성능 최적화 부분이 크지 않다고 생각하며, 또한 에러 추적 부분이 더 중요하다고 생각합니다.
또한, return await같은 경우 순차적 비동기 처리를 위해 필수적으로 필요한 상황이 발생합니다. 그렇기에, 프로젝트에 return awaitno return await가 같이 있기 때문에 휴먼 에러가 발생할 가능성이 있다고 생각합니다.

profile
Typescript를 통해 풀스택 개발을 진행하고 있습니다.

0개의 댓글