Hengxi's 개발 블로그

[JS] 디바운싱(debouncing)과 쓰로틀링(throttling) 본문

개발/JavaScript

[JS] 디바운싱(debouncing)과 쓰로틀링(throttling)

HENGXI 2022. 11. 12. 17:06

이 두 가지 방법 모두 DOM 이벤트를 기반으로 실행하는 자바스크립트를 성능상의 이유로 JS의 양적인 측면, 즉 이벤트(event)를 제어(제한)하는 프로그래밍 기법이다.

 

웹/앱 사용자가 스크롤(scroll wheel), 트릭 패드, 스크롤 막대를 드레깅 한다고 하면, 사용자는 크게 느끼지 못할 수 있으나 이 행위로 인해 수많은 스크롤 이벤트가 발생하게 된다.

이때 매번 스크롤 이벤트에 대한 콜백(callback)이 발생하고 그 콜백이 수행하는 일은 매우 큰 리소스를 잡아먹게 될 것이다.

 

디바운싱과 쓰로틀링은 이벤트가 과도한 횟수로 발생하여 이벤트 핸들러가 무거운 연산을 수 없이 많이 수 행하는 경우에 제약을 걸어 제어할 수 있는 수준으로 이벤트를 발생시키는 것을 목표로 하는 기술이다.

 

디바운싱

연이어 발생한 이벤트를 하나의 그룹으로 묶어서 처리하는 방식으로, 주로 그룹에서 마지막 혹은 처음에 처리된 함수를 처리하는 방식으로 사용되곤 한다.

 

요즘 서비스들은 검색어 치자마자 엔터 없이도 결과가 바로바로 나온다. 만약 'test'를 검색창에 친다고 하자. 엔터 없이도 결과를 즉시 보여주려면 항상 input 이벤트에 대기하고 있어야 한다.

<input id="input" />

document.querySelector('#input').addEventListener('input', function(e) {
  console.log('여기에 ajax 요청', e.target.value);
});

로그가 콘솔에 찍힐 때마다 ajax 요청이 실행된다고 생각하면 되겠다.

문제는 한 글자 칠 때마다 ajax 요청이 실행된다는 것이다. 't', 'te', 'tes', 'test' 모두 요청이 실행된다. 4번이나 요청을 했다(한글같은 조합형 언어는 더 많은 이벤트가 발생할 것이다). 

이와 같은 낭비는 유로 API를 사용했을 때 큰 문제가 된다. 만약 구글지도 API 같은 것을 사용할 때 위와 같이 쿼리를 10번 날리면 어마어마한 손해이다.(쿼리 하나가 다 돈이라고 한다...) 따라서 디바운싱은 비용적인 문제와도 관련이 있다! 마지막 test를 다 쳤을 때 ajax 요청을 보내보자.

 

보통 타자를 연달아 치기 때문에 입력이 다 끝난 후에 요청을 보내면 되겠다. 타자를 칠 때(input 이벤트 발생)마다 타이머를 설정한다. 200ms동안 입력이 없으면 입력이 끝난 것으로 친다(시간은 자율적으로 설정하면 된다). 200ms 이전에 타자 입력이 발생하면 이전 타이머는 취소하고 새로운 타이머를 다시 설정하는 것이다.

var timer;
document.querySelector('#input').addEventListener('input', function(e) {
  // 아직 타이머 수행되지 않았으면(직전 입력으로부터 200ms가 지나지 않았다면) 타이머 취소
  // (타이머가 존재하면, 즉 직전 입력에서 타이머를 생성했다면)
  if (timer) {
    clearTimeout(timer);
  }
  timer = setTimeout(function() {
    console.log('여기에 ajax 요청', e.target.value);
  }, 200);
});

이제 더는 여러 번 호출되지 않는다. 이게 바로 디바운싱이다. 한글은 특성상 마지막에 두 번 호출되는 경우도 있다고 한다.

쓰로틀링

쓰로틀링은 보통 성능 문제에 많이 사용하며, 연이어 발생한 이벤트에 대해 일정한 delay를 포함시켜 연속적으로 발생한 이벤트는 무시하는 방식을 뜻 한다. 즉, delay 시간 동안 호출 된 함수는 무시하는 케이스이다.

 

스크롤을 올리거나 내릴 때 scroll 이벤트가 매우 많이 발생한다. scroll 이벤트가 발생할 때 뭔가 복잡한 작업을 하도록 설정했다면 매우 빈번하게 실행되기 때문에 엄청 렉이 걸릴 것이다. 그럴 때 쓰로틀링을 걸어주는 것이다. 몇 초에 한 번, 또는 몇 밀리초에 한 번씩만 실행되게 제한을 두는 것이다.

 

위와 같이 200ms 제한을 걸어두었다. 타이머가 설정되어 있으면 아무 동작도 하지 않고, 타이머가 없다면 타이머를 설정한다. 타이머는 일정 시간 후에 스스로를 해제하고, ajax 요청을 날리게 하면 된다.

var timer;
document.querySelector('#input').addEventListener('input', function (e) {
   // 타이머가 없을 때만 타이머 설정.
   // 만약 200ms가 지나서 해당 함수를 실행하면 타이머는 사라진다. (timer = null)
   // 만약 타이머 설정 후 200ms가 지나지 않았다면 아무 일도 일어나지 않는다.
   // 따라서 최소 200ms 마다 한번씩만 아래의 코드가 실행된다.
  if (!timer) {
    timer = setTimeout(function() {
      timer = null;
      console.log('여기에 ajax 요청', e.target.value);
    }, 200);
  }
});

이제 초소 200ms 마다 요청을 보낸다. 물론 ajax 검색은 디바운싱으로 처리하는 게 더 나은 것 같다. 하지만 중간 중간 검색 결과도 보여주고 싶다면 쓰로틀링을 쓰는 것이 맞겠다.

lodash를 사용한 debounce, throttle 적용

lodash에서는 위에서 설명한 디바운싱과 쓰로틀링을 쉽게 적용할 수 있도록 debounce, throttle 메소드를 제공한다.

디바운싱

import { debounce } from 'lodash';

const DebounceInput = () => {
  const handleChange = debounce(({ target }) => {
    console.log('debounce', target.value);
  }, 500);

  return <input type="text" onChange={handleChange} />;
};

export default DebounceInput;

쓰로틀링

import { throttle } from 'lodash';

const ThrottleInput = () => {
  const handleChange = throttle(({ target }) => {
    console.log('throttle', target.value);
  }, 500);

  return <input type="text" onChange={handleChange} />;
};

export default ThrottleInput;
Comments