[JS]디바운스와 쓰로틀

PEPPERMINT100·2020년 11월 5일
1

서론

올 해 초에 경험 삼아 프로그래머스의 코딩 및 과제 테스트를 봤습니다. 지금까지 공부해온 길이 맞는지 글고 부족한 점이 무엇인지에 대해 알고 싶었고 결과적으로 부족한 부분이 아주 많았고 그것을 느끼게 해준 점 하나가 바로 디바운스입니다. debounce 기법을 사용하여 REST api에 요청을 해야 했는데 전혀 들어본 적이 없는 단어 였습니다. 레이지-로딩처럼 들어본적은 있지만 실제로 구현해본 적이 없는 기술이 있을지도 모른다 또는 내 코딩 실력이 부족해서 구현하지 못했다 정도는 예상했지만 전혀 들어보지도 못한 미지의 부분이 있을 줄은 생각도 못했습니다. 그 때의 기억을 떠올리며 디바운스 그리고 디바운스와 비슷한 맥락의 쓰로틀 기법도 알아보도록 하겠습니다.

디바운스가 필요하고 쓰로틀링이 필요할까요. 그것은 사용자의 실수 예방과 성능을 개선하기 위함입니다. 간단하게 설명하자면 디바운스는 여러 요청 중 마지막 요청만을 실행하도록 합니다. 쓰로틀링은 일정 시간동안에는 하나의 요청만을 실행하도록 해줍니다. 이는 성능상에 있어서 이점을 가져 올 수 있습니다. 이제 구현을 위해 프로젝트 폴더를 만들고 app.js와 index.html 파일을 생성하여 다음과 같이 작성해줍니다.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <button id="button">Click!</button>
    <script src="./app.js"></script>
  </body>
</html>

index.html

const button = document.getElementById("button");
button.addEventListener('click', ()=>{console.log('clicked!)})
                                                  ```
_app.js_
버튼을 누르면 `clicked`라는 문자가 콘솔에 계속 출력됩니다.
이렇게 하면 일단 기본적인 세팅이 끝난 것입니다.
### 디바운싱
어떤 쇼핑몰을 이용하는 한 사용자가 장바구니에 여러 물건을 담아서 구매하기 요청을 했습니다. 근데 실수로 구매하기 버튼을 두 번 눌렀습니다. 그렇게 했다고 구매 요청이 두 번가서 쇼핑몰 DB에 이중 구매 내역이 남고 결제가 두 번되어버린다면 어떨까요? 당연히 안됩니다. 따라서 우리는 여기서 디바운스 기법을 적용할 필요가 있습니다. 디바운스 기법은 중복된 같은 요청 중 가장 마지막 요청만을 실행하게 합니다.
```javascript
const button = document.getElementById("button");
const debounce = (fn, delay) => {
  let timeout;
  return (...args) => { 
    if (timeout) {
      clearTimeout(timeout);
    }
    timeout = setTimeout(() => {
      fn();
    }, delay);
  };
};
button.addEventListener(
    "click",
    debounce((e) => {
       console.log('You have purchased some items') 
    }, 2000)
    );
app.js에 위와 같이 코드를 작성합니다.
debounce((e) => {
       console.log('You have purchased some items') 
    }, 2000)

addEventLisnter에서 click할 때마다 아래 디바운스 함수를 실행해주도록 합니다. 디바운스 함수에는 2가지 파라미터가 들어갑니다. 하나는 또 다른 함수이고 하나는 딜레이 시간입니다.

const debounce = (fn, delay) => {
  let timeout;
  return (...args) => { // ...args는 함수에 들어온 파라미터들입니다.(여기선 e)이를 통해 내부 함수에서 e에 접근할 수 있게 됩니다.
    if (timeout) {
      clearTimeout(timeout);
    }
    timeout = setTimeout(() => {
      fn();
    }, delay);
  };
};

그리고 디바운스 함수를 작성합니다. setTimeout을 통해 마지막 요청의 2초후에 디바운스의 첫 번째 파라미터인 fn을 실행하게 합니다.
디바운스는 자바스크립트의 클로저(많은 함수형 언어들이 가지고 있는 특징입니다.)를 잘 이용한 기법입니다. 코드를 잘 보면

let timeout // undefined

으로 먼저 timeout을 외부 변수로 지정을 해줍니다. 이렇게 하고 클로저의 특징인 내부함수에서 외부 변수로의 접근이 가능함을 이용하여

if (timeout) {

이 부분에서 timeout을 체크합니다. 가장 최초의 클릭에서 timeout은 undefind 겠죠? 따라서 if문은 무시됩니다.
따라서

timeout = setTimeout(() => {
      fn();
    }, delay);

timeout이 2초 후에 실행이 되도록 합니다.
그런데 여기서 사용자가 실수로 버튼을 또 누릅니다. 하지만 timeout은 이제 setTimeout으로 지정이 되었기 때문에

if (timeout) {
      clearTimeout(timeout);
    }

if문 내의 메소드인 clearTimeout이 실행되고 2초후 실행될 fn은 무효가 되고 또 다시 두번째 클릭에 의한 내부 함수가 실행이 되어 2초후에 실행되는 fn이 setTimeout을 통해 예약이 됩니다.
실제로 버튼을 계속 누르면 가장 마지막 요청만이 실행되는 것을 확인할 수 있습니다.
쓰로틀링
쓰로틀링 역시 사용자의 실수를 방지하여 불필요한 요청을 없애 성능을 개선하는 역할을 해줍니다. 디바운스가 가장 마지막 요청만을 받아서 실행했다면 쓰로틀링은 어떠한 시간동안 단 한번만의 요청을 받는 것을 의미합니다.

const button = document.getElementById("button");
const throttle = (fn, delay) => {
  let last = 0;
  return () => {
    const now = new Date().getTime();
    if (now - last < delay) {
      return;
    }
    last = now;
    return fn();
  };
};
button.addEventListener(
  "click",
  throttle((e) => {
    console.log("clicked!");
  }, 2000)
);

이벤트 리스너부분은 비슷합니다. 2000ms 동안은 여러 번 요청이 들어와도 단 한번만 실행하게 도록 해줍니다.

let last = 0; 

처음 클릭 시의 시간을 0으로 지정해주고

const now = new Date().getTime();

내부 함수에서 클릭한 시간을갱신합니다.

if (now - last < delay) {
      return; 
    }

만약 클릭한 시간과 처음 클릭시의 시간차이가 딜레이 즉 우리가 정한 2000ms보다 작다면, 즉 처음 클릭 후 2000ms의 시간이 지나지 않는다면 아무 일도 일어나지 않습니다.
하지만 2000ms가 지나는 순간

last = now;

last가 갱신되고 이 last는 클로져의 특징 덕에 그 다음 실행때는 if문을 무시하고

return fn();

fn()이 실행됩니다.
실제로 버튼을 계속 클릭해보면 2초마다 한 번씩만 실행이 되는 것을 볼 수 있습니다.

결론

디바운스 기법과 쓰로틀링 기법은 프론트엔드에서 사용자의 실수를 예방하고 필요 없는 함수가 실행되지 않도록 하여 성능을 개선시키는 역할을 합니다. 백엔드에서도 데이터를 검증하고 무결성을 확인하고 인덱싱을 통한 성능 개선을 하는 것 처럼 프론트 단에서도 JS를 이용하여 디바운스, 쓰로틀링, 레이지-로딩 기법을 적용하고 리액트를 사용한다면 useMemo 등을 이용해 UI 성능을 개선할 수 있습니다.

profile
기억하기 위해 혹은 잊어버리기 위해 글을 씁니다.

1개의 댓글

comment-user-thumbnail
2021년 4월 7일

잘읽었습니다.

답글 달기