회사에서 동료의 코드를 리뷰 진행하다가 DB 조회하는 비동기 함수를 await 없이 return 하는 부분이 있어, 동료와 해당 부분에 대해 이야기를 했습니다.
기존에 return await와 no 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();
};
학습한 자료에 의하면 각각의 장점은 아래와 같습니다.
return await
를 사용하는 경우, 함수가 Promise가 해결되기를 기다린 후 결과값을 반환 합니다. 그 에 반해 return만 사용하는 no return await
같은 경우 즉시 Promise를 반환하고, 호출자가 결과를 기다립니다.return await
를 생략하면 코드가 더 간결해지고 읽기 쉬워집니다.return await
를 사용하지 않으면 오류 발생 시 stack trace가 더 간단해집니다. 위 장점 중 간결성은 공감을 하지만, 명확성 같은 경우는 조직, 서비스, 구성원 별로 달라질 수 있을 것 같습니다.
그 외 공감은 되지만, 정확한 결과를 봤으면 하는 부분이 있어 테스트를 진행해봤습니다.
위 예시 코드를 통해 테스트를 진행한 결과 값입니다.
테스트를 진행한 코드는 아래와 같습니다.
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 await
와 no return await
가 같이 있기 때문에 휴먼 에러가 발생할 가능성이 있다고 생각합니다.