코드를 작성한 것들 중에 async/await을 남발해서 동기적으로 작동하게 되어 코드가 비효율적으로 동작한다는 것을 알게되었다.
그래서 리팩토링 하고자 한다.
1. 기존코드
export const dailyCalorieService = async ({ foods, userId }: IUserDailyCalorieService) => {
const userRepository = AppDataSource.getRepository(User);
const foundUser = await userRepository.findOne({ where: { id: userId } });
const bodyMassIndexRepository = AppDataSource.getRepository(BodyMassIndex);
const foundBodyMassIndex = await bodyMassIndexRepository.findOne({
where: { userId },
order: { createdAt: 'DESC' },
});
const dailyCalorieRepository = AppDataSource.getRepository(DailyCalorie);
const foodArrayToString = await foodSentence(foods);
const openAIResponse = await openAI(foodArrayToString); // 문장에서 parse 해야함
const { carbohydrate, protein, lipid, calorie } = await parseGPTSentence(openAIResponse);
await transactionRunner(async (queryRunner) => {
const bmiRepo = dailyCalorieRepository.create({
carbohydrate: carbohydrate,
protein: protein,
lipid: lipid,
calorie: calorie,
bmiId: foundBodyMassIndex.id,
foods: foods,
});
await queryRunner.manager.save(bmiRepo);
});
};
예외처리 부분은 다 제거하고 async/await 부분만 코드를 남겼다.
코드를 보면 비동기부분을 모두 await으로 처리했다.
이러면 promise가 순차적으로 Micro Queue에 들어가서 Event Roof가 CallStack이 비어있으면 Micro Queue에서 순차적으로 1개씩 Event Roof에 밀어넣는 식으로 작동하기에 처리시간이 생각 이상으로 오래걸릴 수 있다.
2. Promise.allSetted 사용
The Promise.allSettled() static method takes an iterable of promises as input and returns a single Promise
This returned promise fulfills when all of the input's promises settle (including when an empty iterable is passed), with an array of objects that describe the outcome of each promise.
다중의 Promise 를 동시에 처리하면서 성공한건 resolve를 던지고 실패한건 reject을 던지기 때문에 에러처리도 간편하게 사용할 수 있다.
export const dailyCalorieService = async ({ foods, userId }: IUserDailyCalorieService) => {
const userRepository = AppDataSource.getRepository(User);
const bodyMassIndexRepository = AppDataSource.getRepository(BodyMassIndex);
const dailyCalorieRepository = AppDataSource.getRepository(DailyCalorie);
try {
const [foundUser, foundBodyMassIndex, foodArrayToString] = await Promise.allSettled([
userRepository.findOne({ where: { id: userId } }),
bodyMassIndexRepository.findOne({ where: { userId }, order: { createdAt: 'DESC' } }),
foodSentence(foods),
]);
} catch (error) {
throw new ErrorResponse(ERROR_CODE.INTERNAL_SERVER_ERROR);
}
const openAIResponse = await openAI(foodArrayToString.value);
const { carbohydrate, protein, lipid, calorie } = await parseGPTSentence(openAIResponse);
await transactionRunner(async (queryRunner) => {
const bmiRepo = dailyCalorieRepository.create({
carbohydrate: carbohydrate,
protein: protein,
lipid: lipid,
calorie: calorie,
bmiId: foundBodyMassIndex.value.id,
foods: foods,
});
await queryRunner.manager.save(bmiRepo);
});
};
위처럼 한번에 처리할 수 있는 즉, 다른함수로부터 파라미터를 받지 않는 코드를 한번에 돌려서 처리시간을 단축시킬 수 있다.
openAI 함수와 parseGPTSentence 함수는 파라미터를 받아야해서 await으로 처리했다.
즉, callstack이 비어있을때 1개의 promise로 묶어서 Micro Queue에 넣어버리니 Event Roof가 CallStack이 비어있을때 이 묶음 Promise를 한번에 CallStack으로 넣어버리기 때문에 Micro Queue 와 CallStack 의 이동시간을 낮춰버린다.
3. Promise.allSetted 단점
1. 에러 처리 : Promise.allSettled는 모든 프로미스의 성공 또는 실패 여부와 상관없이 각 프로미스가 해결되거나 거부될 때까지 기다립니다. 따라서 모든 프로미스가 성공적으로 해결되지 않을 수 있습니다. 따라서 모든 프로미스의 결과를 확인하고 적절히 처리해야 합니다.
2. 성능: Promise.allSettled는 모든 프로미스의 해결을 기다리기 때문에 모든 프로미스가 완료될 때까지 기다려야 합니다. 따라서 프로미스의 수가 많거나 작업이 오래 걸릴 경우 성능에 영향을 줄 수 있습니다.
3. 메모리 사용 : 모든 프로미스의 결과를 기다리므로, 이것은 메모리 사용량을 높일 수 있습니다. 특히 프로미스가 많거나 결과가 큰 경우에는 더 많은 메모리가 필요할 수 있습니다.
4. ES6 지원 : Promise.allSettled는 ES2020부터 지원되기 때문에 이전 브라우저나 환경에서는 지원되지 않을 수 있습니다. 이 경우에는 Polyfill을 사용해야 합니다.
위의 설명대로 Promise.allSetted에 들어간 함수가 처리시간이 너무 오래걸리면 차라리 따로 처리하는게 나을 수 있다.
모든 처리가 끝나야 Promise.allSetted가 끝나기 때문이다.
참고
'JaveScript > ExpressJS' 카테고리의 다른 글
redis connection timeout error (0) | 2024.04.11 |
---|---|
APM tool을 Expressjs에 연동하기 (0) | 2024.04.10 |
유효성검사 중복코드 리팩토링하기 (0) | 2024.03.26 |
if-else => switch-case 리팩토링 (0) | 2024.03.25 |
Redis로 Refresh Token 관리 (1) | 2024.01.15 |