
내가 백엔드 인턴으로 프로젝트의 기존 코드를 살펴봤을 때, 모든 배열 비동기 작업들이 reduce로 처리되고 있었다.
reduce는 이전의 결과값을 다음 실행에 넘겨주어 합계 같은 것을 처리할 때 사용하는 게 아닌가? 라는 생각을 했고, 내가 보기엔 forEach나 filter 와 같은 배열 메서드가 더 올바른 함수의 쓰임이였다.
그러다가 개발을 하던 중에 배열 비동기 작업 로직을 짤 때가 있었다.
여기서 나는 사수와는 다르게 reduce가 아닌 filter 를 통해 비동기 작업을 처리하고 특정 조건을 만족하는 object만 걸러내려고 했다.
다음과 같이 filter 배열 메서드를 통해 time이 1인 애들만 걸러서 사용하려고 했다.
logArray.filter(async (log) => {
if (!log.serviceUuid) {
throw new CustomException("서비스 uuid가 없습니다.");
}
const time = await this.serviceLogRepository.getCountLogByServiceUuid(log.serviceUuid);
console.log(log["매칭 ID"], time, log["서비스 유형"]);
return time == 1;
})
그러나 해당 코드는 내 생각과는 다르게 동작했다.
위의 코드를 보면 filter 함수 안에 현재 getCountLogByServiceUuid 라는 비동기 함수가 들어가있다. 나는 async 와 await 처리를 전부했기에 순차적으로 비동기 작업이 진행될 줄 알았다.
그러나 결과는 이상한 값이 나왔다. time 이 1도 있고, 2도 있어서 사실상 filter 메서드가 제대로 동작하지 않았음을 알 수 있다.
문제의 원인은 filter 메서드 내의 비동기 함수였다.
이는 비단 filter 메서드만의 문제가 아닌 reduce를 제외한 모든 배열 메서드에서 발생할 수 있는 문제이다.
Javascript의 Array.prototype.배열메서드 는 내부에 async와 await 키워드를 통해 비동기 함수를 설정하더라도 순차적으로 비동기 함수가 진행되지 않는다.
이는 배열 메서드가 동작하는 방식에 따른 결과이다.
배열 메서드는 우리가 생각하는 순차적 비동기 처리처럼 이전 callback 함수가 끝나고 실행되지 않는다.
배열 메서드는 그저 배열 원소를 돌면서 callback 함수를 실행시킬 뿐이다.
따라서 우리가 생각하는 순차적 비동기 처리가 이루어지지 않는 것이다.
즉, Array.prototype.filter는 동기적으로 동작하므로, async 함수를 사용할 경우 반환되는 프로미스는 항상 true로 평가되어 내 생각대로 동작하지 않았다.
이 문제를 해결하려면, 먼저 Array.prototype.map과 Promise.all을 사용하여 비동기 작업을 완료시킨 후 이후 매핑 작업을 실행하면 된다.
아니면 Reduce 함수를 통해서 진행할수도 있다.
(사수 분이 배열 비동기 작업에서 reduce 함수를 쓰신 이유가 있었다…사수님 갓갓…⭐⭐)
reduce 함수는 제대로 동작하는 걸까?reduce 함수는 배열의 각 요소에 대해 제공된 리듀서(reducer) 함수를 실행하며, 단일 출력 값으로 줄여나간다.
reduce의 특징 중 하나는 이전 호출의 결과를 다음 호출의 입력으로 전달하는 것이다.
이러한 특성 때문에 reduce는 순차적인 작업에 매우 적합하다!!
즉, 다른 배열 메서드처럼 그저 배열 원소를 돌면서 callback 함수를 실행시키는 것이 아닌, reduce 는 이전의 함수 결과값이 필요하므로 이전의 callback이 종료돼야 다음 callback이 실행되어 우리가 생각한 순차적 비동기 처리가 된다.
예를 들어, 다음과 같은 코드를 생각해보자:
javascriptCopy code
const tasks = [asyncTask1, asyncTask2, asyncTask3];
const result = tasks.reduce(async (acc, task) => {
const previousResult = await acc;
return task(previousResult);
}, Promise.resolve(initialValue));
위 코드에서, asyncTask1의 결과가 asyncTask2의 입력으로 사용되고, asyncTask2의 결과가 asyncTask3의 입력으로 사용된다. 이렇게 reduce는 순차적으로 비동기 작업을 처리한다.
이러한 이유로, reduce는 순차적인 비동기 작업을 처리할 때 다른 배열 메서드보다 더 적합하다.
결론적으로, reduce의 동작 방식은 이전 작업의 결과를 다음 작업의 입력으로 사용하는 순차적인 특성 때문에 비동기 작업에서도 잘 동작한다. 이 특성은 forEach, map, filter와 같은 다른 배열 메서드와는 다르다. 따라서 순차적인 비동기 작업을 처리할 때는 reduce를 사용하는 것이 좋다!!
for문이나 for …of 문으로도 똑같이 비동기 작업 처리가 가능하다.
유익한 글이었습니다.