Debouncing & Throttling

박정호·2023년 1월 29일
0

JS

목록 보기
24/24
post-thumbnail

🚀 Start

디바운싱스로틀링은 애플리케이션 및 브라우저 성능을 최적화하기 위한 자바스크립트 기술이다.


필요한 기술인가?

예를 들어 마우스 움직임, 스크롤링, 검색창 텍스트 입력 등에 의해 발생하는 이벤트가 있다.

이런 상황에서 모두 이벤트 발생을 처리하면 다음과 같은 문제점이 발생한다.

  • 서버/클라이언트 리소스 낭비
  • 서비스 성능 저하
  • 사용자 경험 저하

반대로, 이벤트 제어를 통해 처리량을 조절하게 되면 다음과 같은 효과를 기대할 수 있다.

  • 서버/클라이언트 리소스 절약
  • 비용 절감
  • 사용자 경험 개성

실생활 예제

아파트에 하나의 엘리베이터가 존재한다. 사람들이 한명씩 올때마다 엘리베이터가 출발하면 어떨까? 엘리베이터는 한사람마다 모든 층을 왔다갔다하며 계속 동작하게 되어 성능이 저하될 것이다.

Debouncing
: 앨리베이터가 5초 뒤에 출발한다는 설정이 되어 있어 사람들이 계속 탄뒤에 5초 동안 아무런 탑승 인원이 없으면 출발함으로써 사람들을 그룹화 시켜서 출발한다.

Throttling
: 앨리베이터가 5초마다 출발한다는 설정이 되어 있어 앨리베이터가 일정한 간격을 갖고 출발 한다.

사람 탑승을 기준으로 엘리베이터가 동작하는 것이 아니라 엘리베이터의 설정을 기준으로 동작하면 훨씬 수월한 엘리베이터 동작이 될 것이다.



⭐️ Debounce

Debounce는 이벤트를 그룹화하여 특정시간이 지난 후 하나의 이벤트만 발생하도록 하는 기술이다.

다시말해 순차적 호출을 하나의 그룹으로 그룹화 시킨다.

즉, 마지막 이벤트가 실행되고 나서 일정시간동안 해당 이벤트가 다시 실행되지 않으면 해당 이벤트의 콜백함수를 실행한다.

따라서, Debounce는 마지막 액션에 대한 처리가 중요한 경우에 활용할 수 있다. 특히 유료 API를 사용할 때는 요청 하나하나가 모두 돈이기 때문에 효과를 볼 수 있으며, 주로 연이어 발생하는 이벤트를 단일 이벤트로 만들고 싶은 곳에 사용하면 좋을 것이다.


Code

  • callback: 실행 대상이 되는 콜백 함수

  • limit: 얼마 후에 함수를 실행할 지 결정하며 millisecond 단위

    • limit 이내에 함수가 반복 호출될 경우 timeoutclearTimeout되므로 실행되지 않는다.
  • 함수의 반복 호출이 멈춰진 경우 clearTimeout이 실행되지 않기 때문에 limit 밀리초 후에 callback.apply가 실행되게 된다.

    • applythis의 범위를 지정할 수 있다. 여기서 this의 범위로 콜백 함수가 실행될 때 그 컨텍스트의 this가 배정.
function debounce(callback, limit = 100) {
    let timeout
    return function(...args) {
        clearTimeout(timeout)
        timeout = setTimeout(() => {
            callback.apply(this, args)
        }, limit)
    }
}

Resizing

우리가 브라우저 크기를 조정할때 변경하고 있을 때마다의 크기에 관심이 있을까?
우리는 마지막에 조정한 브라우저의 최종 크기 값만 원할 것이다.

👍 아주 좋은 예시: Resizing - Codepen



⭐️ Throttle

Throttle은 이벤트를 일정 주기마다 발생하다록 하는 기술이다.

다시말해, 마지막 함수가 호출된 후 일정 시간이 지나기 전에 다시 호출되지 않도록 하는 것이다.

따라서, 특성 자체가 실행 횟수에 제한을 거는 것이기 때문에 일반적으로 성능 문제에 많이 사용되며, 특히 스크롤을 올리고 내릴 때 자주 발생하는 scroll 이벤트 핸들러에 적절할 것이다. 사용자가 스크롤을 멈춰야만 이벤트를 발생시키는 Debounce보다는 Throttle이 적합할 것이다.


  • callback: 실행 대상이 되는 콜백 함수

  • limit: 얼마 간격으로 함수를 실행할 지 결정하며 millisecond 단위

  • waiting 상태가 true 인 경우는 if문이 실행되지 않는다.

  • 최초 함수가 호출되었을 때 waitingfalse이며 if문이 실행.

    • 이 때 콜백 함수를 실행한 뒤 다시 waitingtrue가 된다.
      waitingtrue가 되었을 때 limit 밀리초 후에는 waiting이 강제로 false가 되고, 다시 콜백 함수가 실행.
  • applythis의 범위를 지정할 수 있다.

    • 여기서 this의 범위로 콜백 함수가 실행될 때 그 컨텍스트의 this가 배정
function throttle(callback, limit = 100) {
    let waiting = false
    return function() {
        if(!waiting) {
            callback.apply(this, arguments)
            waiting = true
            setTimeout(() => {
                waiting = false
            }, limit)
        }
    }
}

👍 아주 좋은 예시: Moust Event - Codepen



👉 Throttle vs Debounce

ThrottleDebounce 의 차이점은 이벤트를 언제 발생 시킬지의 시점 차이이다.

Debounce 는 입력이 끝날때까지 무한적으로 기다리지만, Throttle 는 입력이 시작되면, 일정 주기로 계속 실행한다.


검색창 구현

이 두가지를 가장 잘 비교할 수 있는 것이 input에 값을 입력하는 검색창 기능일 것이다.

👎 일반적인 input 구현

document.querySelector('#input').addEventListener('input', function(e) {
  console.log( e.target.value);
});

// 결과: 입력마다 요청
// a
// ap
// app
// appl
// apple
------------------------------------------------------------------------------------

👍 debouncing input 구현

var timer;
document.querySelector('#input').addEventListener('input', function(e) {
  if (timer) {
    clearTimeout(timer);
  }
  timer = setTimeout(function() {
    console.log(e.target.value);
  }, 200);
});

// 결과: 2초후에 아무런 입력 없으면 입력해놨던 이벤트 값 한번에 요청
// apple
------------------------------------------------------------------------------------

👍 Throttling input 구현

var timer;
document.querySelector('#input').addEventListener('input', function (e) {
  if (!timer) {
    timer = setTimeout(function() {
      timer = null;
      console.log( e.target.value);
    }, 200);
  }
});
// 결과: 2초마다 입력된 값 요청
// ap
// appl
// apple

위의 예시를 보면 알 수 있듯이 Debounce는 1번만 호출되며 성능상에 더 유리한 부분이 있고, Trottle는 검색되는 경험을 통해 자동완성 기능도 구현할 수 있는 사용자 측면에서 더 유리한 부분이 있다.

물론 debounce의 시간을 짧게 가져가 Thottle와 비슷한 효과를 낼 수는 있지만, 결국 시점에는 차이가 날 수 밖에 없으므로 어떤 기능을 구현할 것이냐에 따라 시점을 잘 고려하여 사용하면 좋을 것 같다.



🧐 React? Lodash?

React에서는 주로 Lodash라는 라이브러리를 사용하여 debounce, Throttle을 사용한다.

Lodash는 주로 array, collection, data 같은 데이터의 구조를 간편하게 함수형으로 다룰 수 있게 해주는데, Lodash에서도 debounce, Throttle 개념이 적용되어 있다.

import _ from ‘lodash’;
document
 .getElementById(“search”)
 .addEventListener(‘keyup’,
   _.debounce((e) => {
    console.log(e.target.value);
   }, 300)
);

import _ from ‘lodash’;
document
 .getElementById(“search”)
 .addEventListener(‘keyup’,
   _.throttle((e) => {
     console.log(e.target.value);
   }, 300)
);

💡 참고하자!
👉 [LODASH] 📚 Lodash.js 소개 및 ES6 자바스크립트와 비교
👉 lodash/debounce.js
👉 lodash/throttle.js
👉 Throttle 와 Debounce 개념 정리하기


하지만, 다음과 같은 문제점이...

Debounce의 경우 유저가 계속 입력하는 경우 화면 업데이트는 계속하여 지연 될 것이다. 결국 작업을 계속 뒤로 미루는 것 뿐이게 되며, 딜레이 시간을 짧게 설정한다면 Debounce를 사용하는 의미가 없어진다.

Throttling의 경우 일정 시간 내에 연속적으로 입력하는 것이 아닌 중간에 입력하지 않으면 무의미하게 기다리는 시간이 생길 것이며 결국 불필요한 요청이 일정시간마다 반복될 것이다.

React에서의 lodash 사용은 설치, timeout 설정 등 유의 할 점이 생긴다.

💡 react에서 lodash를 사용 할때 유의할 점
리렌더링 되는 컴포넌트 내에 debounce를 정의한 함수가 있고, 해당 컴포넌트가 state에 따라 리렌더링이 된다면 debounce 함수도 재생성되면서 debounce가 초기화 된다는 점.

이 버그를 방지하기 위해서는 useCallback을 이용해야하고, useCallback을 이용하여 state가 바뀜에도 debounce함수는 재생성이 되지 않도록 하여 debounce가 초기화 되지 않도록 막는다.


React v.18 useTransition, useDeferredValue의 등장!

이들을 이용하면 함수 실행 자체의 우선 순위를 지정할 수 있고, 값의 업데이트 우선순위를 지정하는 것이기 때문에 debounce, throttling처럼 시기를 정해놓아 무조건적으로 발생하는 요청 등의 단점을 보완해준다.

👉 React Hooks - useTransition / useDeferredValue



🔗 Reference
👉 Throttle 와 Debounce 개념 정리하기
👉 Throttle, Debounce & Difference
👉 Debouncing & Throttling in JavaScript

profile
기록하여 기억하고, 계획하여 실천하자. will be a FE developer (HOME버튼을 클릭하여 Notion으로 놀러오세요!)

0개의 댓글