map + filter vs flatMap
map + filter 대신 flatMap을 선택한 이유
실무에서 Promise.allSettled 결과를 다루다 보면 실패한 케이스만 추려내야 하는 순간이 꽤 자주 나온다.
예를 들면 이런 상황이다.
- 여러 API를 병렬 호출한다.
- 일부는 성공하고 일부는 실패한다.
- 실패한 대상만 모아서 후처리하거나 로그를 남겨야 한다.
이때 자연스럽게 이런 코드를 작성하게 된다.
처음에 썼던 코드 — map + filter
const failedTargets = results
.map((result, index) => {
return result.status === 'rejected'
? {
error: result.reason,
target: targetList[index],
}
: undefined;
})
.filter(Boolean);
굉장히 흔한 패턴이다.
map으로 변환하고- 필요 없는 값은
undefined로 만든 뒤 filter(Boolean)으로 제거한다.
처음엔 별 생각 없이 썼다. JS에서 워낙 자주 보던 패턴이니까.
그런데 TypeScript에서 뭔가 찜찜하다
이 코드가 TS strict 모드에 들어가면 미묘한 문제가 생긴다.
filter(Boolean)은 런타임 동작일 뿐이다.
타입 시스템은 이걸 완전하게 이해하지 못한다.
결과 타입은 여전히 이렇게 나온다:
({ error: any; target: string } | undefined)[]
즉, undefined가 남아있다고 판단한다.
그래서 결국 이렇게 타입 가드를 추가하게 된다.
.filter(
(v): v is { error: any; target: string } => v !== undefined,
);
여기서부터 조금 이상해진다.
- 애초에
undefined를 만들지 않으면 되지 않나? - 왜 일부러 만들고 다시 제거하고 있지?
코드가 살짝 “절차적으로” 느껴지기 시작한다.
그래서 flatMap으로 바꿨다
const failedTargets = results.flatMap((result, index) =>
result.status === 'rejected'
? [{ error: result.reason, target: targetList[index] }]
: [],
);
이 코드를 보고 처음 든 생각은 단순했다.
“실패한 것만 결과 배열에 넣는다.”
딱 이 문장이 그대로 코드가 된다.
무엇이 달라졌을까?
1️⃣ 순회가 한 번이다
map + filter→ 2번 순회flatMap→ 1번 순회
물론 이 차이는 대부분의 경우 체감 불가다. 여기서 병목이 생길 일은 거의 없다.
하지만 불필요한 중간 상태가 사라진다.
2️⃣ undefined가 존재하지 않는다
이게 가장 중요했다.
map + filter는 이런 사고 흐름이다:
일단 다 변환 → 필요 없는 건 undefined → 나중에 제거
flatMap은 이렇게 생각한다:
필요한 것만 결과로 만든다
중간 쓰레기 값이 없다.
3️⃣ TypeScript 타입이 깔끔해진다
flatMap의 결과 타입은 정확하게:
{ error: any; target: string }[]
추가 타입 가드가 필요 없다. strict 모드에서도 아무 경고가 없다.
리팩터링할 때도 더 안전하다.
성능은 솔직히 중요하지 않다
이 코드는 대부분 이런 맥락에서 사용된다.
- API 병렬 호출
- DB 작업
- 네트워크 I/O
실제 비용은 Promise 내부 작업에 있기에 배열 한 번 더 도는 건 거의 의미 없다. 그래서 선택 기준은 성능이 아니다.
결국 기준은 이것이다
✔ 의도가 코드에 얼마나 잘 드러나는가
✔ 타입 시스템과 잘 맞는가
flatMap은 불필요한 상태를 만들지 않아 타입 시스템과 자연스럽게 어울린다.
언제 어떤 걸 써야 할까?
| 상황 | 추천 |
|---|---|
| 단순 변환 | map |
| 필터링만 필요 | filter |
| 조건에 맞는 것만 결과로 생성 | flatMap |
| TS strict 실무 코드 | flatMap 쪽이 더 안정적 |
정리
JavaScript라면 둘 다 괜찮다. 성능 차이도 사실상 무시 가능하다.
하지만 TypeScript 실무 코드라면
“필요한 것만 결과로 만든다”
이 사고 모델을 그대로 표현하는 flatMap이 더 깔끔하고, 더 안전하고, 더 읽기 좋다.