개발일지/Front-end

Throttling(스로틀링)과 Debouncing(디바운싱)

cotnmin 2023. 2. 12. 21:00

Throttling과 Debouncing는 불필요한 연산을 줄이는 방법으로 이벤트나 비동기 요청을 할 때 주로 사용한다.
예를 들어 DOM를 크게 변경하는 이벤트가 scroll이나 resize 이벤트 같은 곳에 묶여있을 경우, 수많은 함수가 호출되고
키보드 입력을 받는 경우 엔터없이 자동완성을 한다고 했을 때 한 글자 한 글자 입력 시마다 요청을 보내게 될 것이다.


즉, 함수의 호출 비용(시간이나 컴퓨팅 성능이 될 수도 있고, api같은 경우 이용료나 트래픽 비용이 될 수 있다.)이 큰 경우 비용 절감을 위해 성능을 조절하는 기법이다.

 

둘의 차이점은 아래와 같다.

  • Throttling: 함수 호출 후 정해진 시간동안 같은 함수 호출 X
  • Debouncing: 지연 호출되는 함수를 호출 시 이전 호출은 취소하고 마지막 함수만 호출

Throttling은 직관적으로 이해가 쉽지만 Debouncing는 이해가 어렵다. 예제를 통해 알아보자

1. Throttling

Throttling은 일반적으로 첫 함수 실행과 동시에 요청상태값을 변경 후 일정 시간, 행동 뒤에 다시 요청상태값을 변경해 다른 함수의 실행 여부를 판단한다.

 

Throttling을 Javascript로 구현해보자

const throttling = (callback, delay) => {
  let timer;
  let is_called = false;
  return function () {
    if (is_called) return;
    is_called = true;
    callback(...arguments);
    timer = setTimeout(() => is_called = false, delay);
  }
}
/*
주의사항
return 하는 함수에 화살표함수 `() => {}` 형식을 이용하면 arguments를 제대로 가져올 수 없다.
*/

이렇게 만들어주고 사용은 아래와 같이 할 수 있다.

const func = throttling(console.log, 1000);
func('test', 1);
func('test', 2);
func('test', 3);
setTimeout(() => func('test', 4), 900);
setTimeout(() => func('test', 5), 1000);
setTimeout(() => func('test', 6), 1100);

/*
출력 결과
test 1
test 5
*/

결과 해석

  1. func('test', 1); 실행 =>
    is_called는 false였으므로 true로 바뀌고 함수 실행
    1000ms 뒤 is_called = false 하도록 setTimeout 설정
    'test 1' 출력
  2. func('test', 2); 실행 =>
    is_called는 true이므로 return
  3. func('test', 3); 실행 =>
    is_called는 true이므로 return
  4. 약 900ms 뒤 func('test', 4); 실행 =>
    is_called는 true이므로 return
  5. 약 100ms 뒤 setTimeout으로 is_called는 false로 변경
    func('test', 5); 실행 =>
    is_called는 false였으므로 true로 바뀌고 함수 실행
    1000ms 뒤 is_called = false 하도록 setTimeout 설정
    'test 5' 출력
  6. 약 100ms 뒤 func('test', 6); 실행 =>
    is_called는 true이므로 return

2. Debouncing

Debouncing은 함수를 실행 시 해당 함수를 일정 지연시간 또는 행동 뒤에 실행한다.
해당 함수 실행 전 다른 함수가 실행되면 기존 함수는 실행 취소하고 새 함수를 일정 지연시간 또는 행동 뒤에 실행한다.

 

Debouncing을 Javascript로 구현해 보자

const debouncing = (callback, delay) => {
  let timer;

  return function () {
    if (timer) clearTimeout(timer);
    timer = setTimeout(() => callback(...arguments), delay);
  }
}
/*
주의사항
return 하는 함수에 화살표함수 `() => {}` 형식을 사용하지 않고
setTimeout callback에 화살표함수 형식을 사용해야 
arguments를 제대로 전달할 수 있다.
*/

이렇게 만들어주고 사용은 아래와 같이 할 수 있다.

const func = debouncing(console.log, 1000);
func('test', 1);
func('test', 2);
func('test', 3);
setTimeout(() => func('test', 4), 900);
setTimeout(() => func('test', 5), 1000);
setTimeout(() => func('test', 6), 1100);

/*
출력 결과
test 6
*/

결과 해석

  1. func('test', 1); 실행 =>
    timer에 1000ms 뒤 실행하도록 setTimeout 설정
  2. func('test', 2); 실행 =>
    timer에 등록된 setTimeout 해제
    timer에 1000ms 뒤 실행하도록 setTimeout 설정
  3. func('test', 3); 실행 =>
    timer에 등록된 setTimeout 해제
    timer에 1000ms 뒤 실행하도록 setTimeout 설정
  4. 900ms 뒤 func('test', 4); 실행 =>
    timer에 등록된 setTimeout 해제
    timer에 1000ms 뒤 실행하도록 setTimeout 설정
  5. 약 100ms 뒤 func('test', 5); 실행 =>
    timer에 등록된 setTimeout 해제
    timer에 1000ms 뒤 실행하도록 setTimeout 설정
  6. 약 100ms 뒤 func('test', 6); 실행 =>
    timer에 등록된 setTimeout 해제
    timer에 1000ms 뒤 실행하도록 setTimeout 설정
  7. 1000ms 뒤 'test 6' 출력

3. 사용 예시

  1. Throttling은 일단 실행 후 다음 실행 함수를 제어하는 것으로 delay가 길어져도 이용자는 일단 최초 실행된 결과를 볼 수 있다.
    Debouncing은 delay가 길어질 경우 이용자는 함수가 먹통이라고 느낄 수 있다.
    위 예시를 보면 Debouncing은 2100ms 만에 콘솔에 출력됨을 확인할 수 있다.
  2. 리스트 출력처럼 최초 출력이 중요한 경우 Throttling이
    키보드 입력처럼 최종 값이 중요한 경우 Debouncing이 적합하다.
  3. 거래목록을 출력하는 경우 스크롤이 끝에 닿을 때쯤 API를 요청한다.
    이때 Throttling을 사용하지 않는 경우 하단 부에서 스크롤이 발생할 때 지속적인 요청이 보내질 수 있다.
    요청 이후 일정 시간 또는 요청이 도착할 때까지 다음 요청을 보내지 않아야 한다.
  4. 검색창에서 디바운싱을 검색할 때 자동완성을 하여 아래에 보여주고자 한다.
    Debouncing을 사용하지 않는 경우 ,,,디바,디방,디바우,디바운,디바운ㅅ,디바운시,디바운싱 10번의 요청을 보내야 한다.
    만약 오타라도 난다면 더욱 불필요한 자동완성 결과를 요청해야 한다.
    그래서 어느 정도 검색어 입력이 완성되었다고 판단하였을 때 자동완성 목록을 요청해야 한다. 300~500ms의 지연시간 정도가 적절할 수 있다.

기타

Throttling과 Debouncing을 구현할 때 여러 방법이 있지만 위와 같은 형태로 만들 때
일반적인 익명함수 형태(function () {})와 화살표함수(() => {})의 차이를 잘 구분해야 한다.
익명함수 형태에서는 자신의 this, arguments, super 등을 가지지만
화살표함수 형태에서는 부모의 값을 상속받는다.

 

참조: MDN - Arrow_functions

 

 

읽어주셔서 감사합니다. 추가할 내용이나 잘못된 내용은 댓글을 남겨주세요