노트

디바운스

Debounce

프론트엔드#js#performance · 연결된 개념 4개

쉽게 말하면

디바운스는 엘리베이터 문처럼 사람이 계속 타는 동안은 기다렸다가, 잠깐 아무도 안 타면 그때 한 번 닫는 방식이에요. 검색창에 글자를 칠 때마다 요청하지 않고 입력이 멈춘 뒤 한 번만 보내죠.

비유의 한계 사람이 끝없이 타면 문이 영영 안 닫히듯, 호출이 계속 이어지면 실행도 계속 미뤄져요. 스크롤처럼 도중에도 반영해야 하는 일에는 일정 간격마다 실행하는 스로틀이 맞아요.

디바운스(Debounce)는 짧은 간격으로 이어지는 호출을 한 묶음으로 보고, 호출이 멈춘 뒤 정해진 시간이 지나면 한 번만 실행하는 기법이다. 입력처럼 잦게 일어나는 이벤트에 무거운 작업(결과 필터링, 추천어 요청)이 붙을 때 쓴다.

동작

  • 묶음의 첫 호출 시점을 leading edge, 마지막 호출 뒤 대기 시간이 지난 시점을 trailing edge라고 한다
  • 보통 trailing edge에서 한 번 실행한다. 용도에 따라 leading edge나 양쪽 모두에서 실행하기도 한다
  • 대기 시간 안에 새 호출이 오면 같은 묶음이 되고, 대기 시간은 그 호출부터 다시 잰다. 그래서 호출이 끊기지 않으면 실행도 계속 미뤄진다

스로틀(Throttle)과 다른 점

디바운스스로틀
호출이 계속될 때멈출 때까지 기다린다정해진 간격마다 최대 한 번 실행한다
보통 실행하는 때마지막 호출 뒤(trailing edge)묶음의 첫 호출(leading edge)
맞는 일입력이 멈춘 뒤 검색·추천스크롤하는 동안 위치 반영

스크롤 위치에 맞춰 다른 요소를 움직이는 일은 스크롤 도중에도 계속 반영돼야 하므로 스로틀이 맞다. 검색어 추천은 입력이 멈춘 뒤 한 번이면 되므로 디바운스가 맞다.

구현 예

debounce(fn, wait)는 fn을 감싼 새 함수를 돌려준다. 호출될 때마다 이전 예약을 취소하고 wait 뒤로 다시 예약해서, 마지막 호출만 실행된다(trailing edge).

function debounce<A extends unknown[]>(fn: (...args: A) => void, wait: number) {
  let timer: ReturnType<typeof setTimeout> | undefined
  return (...args: A) => {
    clearTimeout(timer)                          // 이전 예약 취소
    timer = setTimeout(() => fn(...args), wait)  // 마지막 호출 기준으로 다시 예약
  }
}
 
const search = debounce((q: string) => console.log('검색', q), 300)
search('r'); search('re'); search('rea')   // 300ms 뒤 '검색 rea'만 한 번 찍힌다

관련 개념

  • React의 useDeferredValue는 시간이 아니라 우선순위로 렌더를 미룬다. 네트워크 요청 수를 줄이는 일은 여전히 디바운스의 몫이다
  • HTMX의 delay 수식어도 디바운스다
  • 서버가 클라이언트별 요청 수를 제한하는 것은 요청 제한으로, 함수 호출을 줄이는 스로틀과는 다른 장치다
  • 디바운스는 타이머를 쓴다. 해제하지 않은 타이머가 무엇을 붙잡아 두는지는 브라우저 JS 메모리 누수 원인

출처: MDN Glossary - Debounce · MDN Glossary - Throttle

연결된 개념

이 노트를 가리키는 문서

뜻이 가까운 노트

  • 콜백

    다른 함수에 인자로 넘겨서 그 함수가 대신 불러 주게 하는 함수. 동기로도, 비동기로도 불릴 수 있다.

  • 이벤트 루프

    자바스크립트를 실행하는 하나의 스레드가 할 일을 큐에서 하나씩 꺼내 처리하는 반복 구조. JS 엔진 자체에는 타이머도 네트워크도 없고, 브라우저(또는 Node.js)가 이 루프를 돌리면서 콜백을 엔진에 넘겨준다.

  • requestAnimationFrame

    "다음 화면을 그리기 직전에 이 함수를 실행해 달라"고 브라우저에 예약하는 API. 콜백은 태스크 큐도 마이크로태스크 큐도 아닌 별도 목록에 들어가고, 이벤트 루프의 렌더링 단계에서 페인트 직전에 한꺼번에 실행된다.

  • 동기·비동기와 블로킹·논블로킹

    동기·비동기는 결과를 언제 어떻게 받느냐, 블로킹·논블로킹은 기다리는 동안 호출한 스레드가 멈추느냐의 문제다.

  • 롱 태스크

    메인 스레드를 50ms 넘게 붙잡는 작업. W3C Long Tasks 명세는 이벤트 루프 태스크(뒤따르는 마이크로태스크 처리 포함), 렌더링 갱신 단계, 이벤트 루프 밖에서 브라우저가 UI 스레드로 하는 일 가운데 50ms를 넘는 것을 롱 태스크로 본다. 태스크는 도중에 끊기지 않으므로 그동안 클릭·키 입력 처리와 화면 갱신이 모두 밀린다(이벤트 루프).

보기 옵션