본문 바로가기
TYLER SONGBlog
블로그 목록
Frontend

switchMap: 이전 요청을 취소하고 최신 요청만 남기는 연산자

switchMap은 새로운 값이 들어올 때마다 이전 내부 Observable의 구독을 취소하고 새 Observable로 전환하는 고차 Observable 평탄화 연산자다. 검색어 입력이나 버튼 클릭처럼 최신 요청 결과만 필요한 상황에서 이전 비동기 작업을 자동으로 정리해준다. 이 실습에서는 순수 JS 환경에서 가짜 API 호출을 만들어 switchMap이

송민성6분 읽기

1. 개념

switchMap은 소스 Observable에서 값이 방출될 때마다 그 값을 새로운 내부 Observable로 매핑하고, 그 내부 Observable을 구독한 뒤 결과를 바깥으로 흘려보내는 연산자다. 여기까지는 mergeMap이나 concatMap과 비슷하지만, switchMap만의 특징은 "전환(switch)"이라는 이름 그대로 새로운 값이 들어오면 이전에 구독 중이던 내부 Observable을 즉시 구독 해지(unsubscribe)한다는 점이다.

즉 switchMap은 항상 가장 최근에 생성된 내부 Observable 하나만 살아있게 만들고, 그 이전 것들은 완료 여부와 상관없이 강제로 취소한다.

2. 왜 사용하는가

사용자가 검색창에 빠르게 타이핑하는 상황을 생각해보자. 글자를 입력할 때마다 서버에 요청을 보내면, 네트워크 지연에 따라 응답이 요청 순서와 다르게 도착할 수 있다. 예를 들어 "abc"를 입력했을 때 "a" 요청보다 "ab" 요청이 늦게 도착하면 화면에는 오래된 "a"의 결과가 마지막에 표시되는 버그가 생긴다.

switchMap을 쓰면 새 입력이 들어오는 순간 이전 요청의 구독을 끊어버리므로, 이전 응답이 늦게 도착하더라도 화면에 반영되지 않는다. 결과적으로 항상 최신 입력에 대한 응답만 신뢰할 수 있다.

3. 동작 원리

switchMap의 내부 동작은 다음과 같은 순서로 이해하면 된다.

먼저 소스 Observable을 구독하고, 값이 하나 도착하면 그 값을 전달받은 프로젝션 함수(project function)에 넣어 새로운 내부 Observable을 생성한다.

그다음 이 내부 Observable을 구독하기 직전에, 만약 이전에 구독해둔 내부 Observable이 아직 살아있다면 그 구독을 즉시 해지한다. 이 시점이 핵심인데, 이전 내부 Observable이 완료되었는지 여부와 무관하게 무조건 취소한다.

그리고 새로 만든 내부 Observable을 구독해서 그 안에서 방출되는 값들을 바깥 Observable로 그대로 흘려보낸다. 만약 소스 Observable에서 또 다른 값이 도착하면 같은 과정을 반복한다.

마지막으로 소스 Observable이 완료되고, 현재 활성화된 내부 Observable도 완료되면 전체 스트림이 완료된다.

4. 예제 (인라인 코드로만)

가짜 API 호출 함수를 먼저 만들어본다. fakeApiCall(query)라는 함수는 new Observable(subscriber => { ... }) 형태로 만들어서, setTimeout으로 1초 뒤에 subscriber.next(query + '의 결과')subscriber.complete()를 호출하도록 구성한다. 그리고 반환값으로 setTimeout을 취소하는 정리 함수(teardown function)를 넣어서, 구독이 해지되면 clearTimeout이 호출되고 콘솔에 ${query} 요청이 취소되었습니다라는 로그가 찍히도록 만든다.

그다음 fromEvent 대신 실습 편의를 위해 of('a', 'ab', 'abc')를 소스로 사용하되, 실제 타이핑처럼 시간 간격을 주기 위해 concatMapdelay를 조합하거나, 더 간단하게는 interval(300)take(3)을 조합해서 300밀리초 간격으로 값을 방출하도록 만든다.

이제 이 소스 Observable에 pipe(switchMap(query => fakeApiCall(query)))를 연결한다. fakeApiCall의 응답 지연 시간은 1초이고 소스는 300밀리초 간격으로 다음 값을 내보내므로, 각 요청이 완료되기 전에 다음 요청이 시작되는 상황이 만들어진다.

마지막으로 subscribe(result => console.log(result))로 구독하면, 콘솔에는 "a 요청이 취소되었습니다", "ab 요청이 취소되었습니다"라는 취소 로그가 먼저 찍히고, 마지막에 "abc의 결과"만 정상적으로 출력되는 것을 확인할 수 있다. 이는 앞의 두 요청이 완료되기 전에 switchMap이 구독을 강제로 해지했기 때문이다.

5. 실무 사용 사례

검색 자동완성 기능에서 사용자가 입력할 때마다 서버에 검색어를 보내고 이전 요청은 취소하는 패턴에 switchMap이 표준적으로 쓰인다. 이 경우 보통 fromEvent(inputElement, 'input'), debounceTime, distinctUntilChanged, switchMap을 순서대로 조합해서 불필요한 요청 횟수를 줄이고 최신 요청만 남긴다.

또한 라우트가 바뀔 때마다 이전 라우트에서 걸어둔 데이터 요청을 취소하고 새 라우트의 데이터만 가져오는 상황, 또는 새로고침 버튼을 연속으로 눌렀을 때 마지막 클릭에 대한 응답만 유효하게 처리하고 싶은 상황에서도 switchMap이 사용된다.

6. 주의할 점

switchMap은 이전 내부 Observable을 취소하는 특성 때문에, 모든 요청의 결과를 빠짐없이 받아야 하는 상황에는 적합하지 않다. 예를 들어 파일 업로드처럼 각 요청이 반드시 끝까지 완료되어야 하는 작업에 switchMap을 쓰면 진행 중이던 업로드가 중간에 취소될 수 있다. 이런 경우에는 mergeMap이나 concatMap을 검토해야 한다.

또한 내부 Observable이 실제로 구독 해지 시 정리 작업(teardown)을 제대로 구현하고 있어야 취소가 의미를 가진다. 예를 들어 HTTP 요청 라이브러리가 구독 해지를 실제 네트워크 요청 중단(abort)으로 연결해주지 않으면, switchMap이 구독은 끊어도 서버 쪽 요청 자체는 계속 처리되고 있을 수 있다.

7. 핵심 정리

switchMap은 소스 값이 새로 들어올 때마다 이전 내부 Observable의 구독을 취소하고 최신 내부 Observable만 유지하는 평탄화 연산자다. 이 특성 덕분에 검색 자동완성처럼 최신 요청 결과만 필요한 시나리오에서 이전 요청의 응답 지연으로 인한 데이터 불일치 문제를 자연스럽게 해결할 수 있다. 다만 모든 요청을 빠짐없이 처리해야 하는 작업에는 맞지 않으므로, 요구사항에 따라 mergeMap이나 concatMap과 구분해서 사용해야 한다.

© 2026 Tyler Song