디바운스와 쓰로틀

이동규 (Justin)·2020년 9월 22일
2

https://levelup.gitconnected.com/debounce-in-javascript-improve-your-applications-performance-5b01855e086 를 참고해 작성하였습니다.

먼저 디바운스와 쓰로틀을 from scratch로 적용시키는 일은 코딩테스트에서 빈번한데(그렇다고 한다), 클로저나 비동기 스코프와 같은 중요한 개념들을 적용하여야 해결이 가능한 문제이기 때문이다!

디바운스와 쓰로틀은 연속되는 액션에 반응하는 함수의 호출을 줄여 optimization을 달성하려는 공통점을 갖는다. 디바운스는 유저가 인터랙션의 시도를 끝마치기 전까지는 아무 일도 하지 않고, 유저가 인터랙션을 끝마쳤을 때 마지막으로 입력받은 인풋을 로직에 전달하고 그에 해당하는 결과물을 내놓는다.

쓰로틀은 유저의 인터랙션 중간중간에 인풋에 해당하는 값을 보여주되, 그 인터벌을 원하는 대로 조정하고 싶은 상황에서 사용될 수 있다. 인풋에 따라 실시간으로 로직을 실행하고 결과물을 반환하여 유저에게 결과물을 보여준다는 것은 같지만 로직이 실행되는 텀을 원하는 대로 조정하여 어플리케이션의 효율성을 높일 수 있는 것이다.

이하는 디바운스의 작동 예제이다. 함수를 인자로 입력받아 debounced 된 함수를 반환하는 HOF(Higher Order Function)의 형태로 작성되었다.

const debounce = (func, wait) => {
  let timeout;

  return function executedFunction(...args) {
    const later = () => {
      clearTimeout(timeout);
      func(...args);
    };

    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
};

코드를 확인해보면, funcwait를 받아 새로운 디바운스 함수를 만들어낸다. 예를 들어 유저의 인터랙션이 5초 간 없을 때에만 인터랙션이 끝났다고 판단하고 함수 a를 실행하기 위해 디바운스 함수를 호출하고, 새로운 함수 debounceA를 만들었다고 하자.

그러면 새롭게 만들어진 debounceA 는 어떻게 작동하는가?

만일 사용자가 debounceA 를 호출하는 인터랙션을 1초에 한번씩 반복하고 있다고 가정하자.

가장 처음으로 debounceA 가 실행될 때, setTimeout이 호출되며 콜백으로 넘겨지는 later 함수가 5초 이후 실행되도록 계획된다. 해당 타임아웃을 멈출 수 있는 객체가 자유변수 timeout으로 할당되고, 함수실행은 끝난다.

이후 5초가 지나지 않은 시점에서 유저의 인터랙션이 일어나 또 다시 debounceA가 호출 될 때, 가장 처음으로 실행되는 것은 clearTimeout(timeout); 이다. 이를 통해 이전에 실행되도록 기다리고 있던 콜백함수의 실행을 멈출 수 있게 된다. 이후 이번 호출에 의해 새롭게 5초의 timeout을 설정하여 setTimeout을 실행한다.

이렇게

5초 이후 later 실행 - (5초가 지나기 전) 5초 이후 later 실행 - ...

무한 반복되다가, 정말 5초가 지난다면 그때 마지막으로 한번의 later 콜백이 실행될 수 있게 된다.

이번엔 쓰로틀의 구현 예제를 보자.

function throttle (func, interval) {
  let timeout;
  
  return function() {
    const context = this;
    const args = arguments;
    
    const later = function () {
      timeout = false;
    };
    
    if (!timeout) { // 쿨타임이 찼다면 (?)
      func.apply(context, args); // 실행해주고
      timeout = true; // 다시 실행 못하게 막고
      setTimeout(later, interval); // 쿨타임을 돌린다..
    }
  }
}

디바운스와 비슷한 모양새로 구현되어 있음을 알 수 있다. 설명은 주석 참조..

추가
디바운스를 무한스크롤링에 적용하려다 보니, 인터랙션의 마지막에만 실행하는 것도 디바운스이지만 인터랙션의 시작에서만 (로직을) 실행하는 것도 디바운스구나, 라는 생각이 들었다. 어찌되었든 여러번의 로직 실행을 사용자에게 정말 필요한 횟수로 줄이고 효율성을 확보한다는 점에서 같다.
이곳에서도 디바운스를 아래와 같이 설명하고 있다.

Debouncing enforces that a function not be called again until a certain amount of time has passed without it being called. For example, "execute this function only if 100 milliseconds have passed without it being called."

lodash 에서 제공하는 debounce는 leading 과 trailing이라는 옵션을 전달해서 처음에만 실행할 것인지, 마지막에만 실행할 것인지 혹은 둘다 할 것인지 선택할 수 있다.둘 다를 선택하는 경우에는 마지막 실행 이후의 타이머만 작동하는 것 같다(trailing처럼 작동, 단 로직은 처음과 마지막 두가지를 실행하고 리턴 - 이 부분은 100% 확실하지 않으나 실험 결과 그렇게 작동하는 듯 하다, 왜냐하면 실행 이후 2초간 모든 실행을 막아버린다면 마지막 실행을 인식조차 할 수 없지 않나?..).

그렇다. 오늘은 디바운스와 쓰로틀을 알아보았다!

이미지 출처

profile
Frontend Developer, JamStack, Ethereum

0개의 댓글