정의
검색창처럼 사용자가 타이핑할 때마다 서버에 요청을 보내는 화면에서, "화면에 결국 남는 결과가 항상 마지막으로 친 검색어의 결과"가 되도록 보장하고 싶을 때 쓰는 패턴이다. 빨리 타이핑하면 요청이 여러 개 겹치는데, 엉뚱하게 이전 검색어의 결과가 화면에 남는 사고를 막는다.
세 가지 도구를 조합한다: debounce(입력이 잠깐 멈출 때까지 기다렸다가 한 번만 요청 — 예: 200ms 안에 또 치면 요청을 미룸)로 호출 횟수를 줄이고, AbortController(진행 중인 fetch 요청을 중간에 취소하는 브라우저 표준 객체)로 이전 요청을 취소하며, 점점 커지는 "최신 요청 id"를 두어 늦게 도착한 응답(out-of-order — 먼저 보낸 요청이 더 늦게 도착하는 것)을 무시한다.
왜 필요한가
빠르게 타이핑하면 여러 요청이 동시에 떠고, 네트워크 지연 때문에 먼저 보낸 요청이 더 늦게 도착할 수 있다. 그대로 화면에 그리면 오래된 결과가 최신 결과를 덮어쓰는 경쟁 조건(race condition — 여러 작업의 완료 순서가 뒤엉켜 결과가 어긋나는 상황)이 생긴다. 또 매 키입력마다 요청하면 서버 부하도 커진다. 이 패턴은 호출 빈도·취소·순서 보장을 함께 다뤄 항상 최신 질의만 반영되는 일관된 UI를 만든다.
동작 원리
- 입력 변경 → debounce 타이머 설정, 이전 타이머는 clear.
- 요청 직전
id = ++ref부여, 응답 도착 시id === ref일 때만 상태에 반영. - 새 요청/언마운트 시 AbortController로 이전 in-flight 요청 abort.
- 결과·상태는 render 중 파생: 마지막 resolved 질의 == 현재 질의인지로 status(idle/loading/ready/error)를 계산하고, 상태 쓰기는 비동기 콜백에서만 한다.
실무 적용
트레이드오프
debounce 윈도가 길수록 요청은 줄지만 반응이 느려지고, 짧으면 반응은 빠르지만 요청이 늘어난다. id 가드만으로도 정합성은 지키지만, AbortController까지 쓰면 불필요한 네트워크·서버 비용을 추가로 줄인다(취소 지원 필요).
사용하면 안 되는 경우
단발성·멱등·순서 무관한 요청이거나, 결과가 누적되어야 하는 경우(무한 스크롤 append 등)에는 "최신만 반영" 규칙이 오히려 데이터를 버린다. 이럴 땐 누적 머지 전략을 쓴다.
흔한 실수
- abort 에러를 일반 에러로 처리해 사용자에게 에러 UI를 노출.
- 요청 id를 state로 둬서 불필요한 렌더 유발(ref가 맞다).
- effect 본문에서 동기 setState로 results를 써서 중복 렌더·깜빡임 발생(render 중 파생이 안전).
관련 개념
- serverless-stateless-execution — 이 요청들이 도달하는 서버 측 검색 함수의 실행 모델.