ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Throttle: 함수가 주기적으로 실행되는지 확인하기
    FrontEnd/JavsScript 2021. 4. 17. 00:21

    명시적인 리턴값이 없는 경우, 함수가 의도대로 작동하는지를 어떻게 확인할 수 있을까 하는 의문이 생겼다.

     

    throttle은 함수와 delay를 인자로 받고, delay 동안에는 실행 요청이 들어오더라도 1회 이상 실행되지 않는 함수를 리턴해준다.

    코드를 작성한 후 인자로 들어온 함수가 정말로 특정 시간 동안 실행되지 않는지 여부를 어떻게 확인할 수 있을까를 고민하다가

    인자 함수가 실행되면 로그를 찍어주면 되겠다는 생각이 들었다.


    잠깐 짚고 넘어가는 throttle과 debounce의 차이

     

    (JavaScript) 쓰로틀링과 디바운싱

    안녕하세요. 이번 시간에는 쓰로틀링(throttling)과 디바운싱(debouncing)에 대해 알아보겠습니다. 원래 예정에 없던 강좌이지만 요청을 받았기 때문에 써봅니다. 프로그래밍 기법 중 하나입니다(아니

    www.zerocho.com

    throttle: 무한스크롤 시에 사용하면 유용하다. 한 번 작동하면 일정 시간 동안은 더 이상 작동하지 못하도록 막는다.

    [lodash throttle source-code]

     

    debounce: 검색어 입력 도중에 결과를 보여주는 경우 사용하면 유용하다. 특정 시간 동안 입력 받은 작업은 하나로 치고, 마지막 작업(혹은 첫번째 작업)만 실행한다.

    [lodash debounce source-code]

     

    lodash에서는 throttle을 debounce에 'leading'과 'trailing' 옵션을 true로 주고 실행하는 방식으로 구현했다.

    문서에 보면 leading(선행)과 trailing(후행) 옵션이 true일 때, delay 동안 1번 보다 많이 실행되는 경우, delay 만큼 시간이 지난 다음에 1번만 실행된다고 되어있다.


     

    먼저 인자로 넘겨줄 함수는 아래처럼 만들었다.

    몇 번째 호출되었는지를 확인해야 하기 때문에 클로저로 nthRun을 넣고, 출력될 때마다 nthRun과 현재 시간을 보여준다.

    function showCallingSequence(name) {
      let nthRun = 0;
      return () => {
        const currentTime = Date.now();
        console.log(`[${name}] ${nthRun++}th ${currentTime}`);
      };
    }

     

    이제 이 함수를 throttle에 넣고, 안 넣고로 나누어 실험군과 대조군을 만들고

    const experimental = showCallingSequence('■Throttle');
    const throttled = throttle(experimental, 500); // throttle 기준이 500이므로 0.5초에 1번만 실행된다
    
    const control = showCallingSequence('□Control');

     

    1초 동안 0.05초 주기로 각각 실행을 시켜본다.

    const timeID = setInterval(() => {
      control();
      throttled();
    }, 50);
    
    setTimeout(() => clearInterval(timeID), 1000);

     

     

     

    먼저 lodash의 throttle로 테스트를 해봤다.

    jsbin에서 lodash를 쓰려면 아래 태그를 html 부분에 추가해주면 된다.

    <script src="https://cdn.jsdelivr.net/lodash/4/lodash.min.js"></script>

     

    jsbin에서는 const, let과 탬플릿리터럴이 잘 안 먹어서 수정했다.

    function showCallingSequence(name) {
      var nthRun = 0;
      return function () {
        var currentTime = Date.now();
        console.log(name + ' ' + nthRun++ + 'thRun ' + currentTime);
      };
    }
    
    var experimental = showCallingSequence('■Throttle');
    var throttled = _.throttle(experimental, 500);
    
    var control = showCallingSequence('□Control');
    
    var timeID = setInterval(() => {
      control();
      throttled();
    }, 50);
    
    setTimeout(() => clearInterval(timeID), 1000);

     

     

    의도대로 _.throttle이 500ms 텀을 두고 실행되고 있는 것을 확인할 수 있다.

     

     

     

    처음에 작성했던 (오류가 있는) throttle 함수

    const throttle = function (func, wait) {
      let isWaiting = false;
    
      return function () {
        if (isWaiting) {
          return;
        }
    
        setTimeout(() => {
          isWaiting = true;
        }, wait);
    
        return func();
      };
    };

    처음 실행하면 isWaiting이 false이므로 setTimeout이 걸리고 isWaiting이 true로 토글된다.

    이후에 다시 isWaiting을 false로 바꿔주는 부분이 없기 때문에(오류 부분) 현재는 특정 시간 이후로 실행을 막는 형태다.

    문제가 있다는 건 알겠는데, 눈에 보이는 게 없어서 정확히 어떻게 문제가 되는지 확인하기가 어렵다.

    위에서 만든 showCallingSequence를 인자로 주고 출력해보면 이렇게 나온다.

     

     

    의도대로 특정 시간(wait) 동안만 실행을 막고 싶다면, setTimeout이 끝나면 isWaiting을 다시 false로 돌려줘야 한다.

    그리고 함수가 실행되는 동안에는 추가 실행을 막아야 하니 return 직전에 isWaiting = true를 추가해준다.

     

     

    (오류를 해결한) throttle 함수

    const throttle = function (func, wait) {
      let isWaiting = false;
    
      return function () {
        if (isWaiting) {
          return;
        }
    
        setTimeout(() => {
          isWaiting = false;
        }, wait);
    
        isWaiting = true;
        return func();
      };
    };

    이렇게 되면 아래처럼 작동하게 된다.

    setTimeout 시작

    -> 비동기 함수이므로 아래 라인 먼저 실행

    -> isWaiting = true 토글

    -> 인자로 받은 함수 실행

    -> setTiemout이 돌아가는 동안은 isWaiting이 true이므로 추가로 호출되더라도 실행이 차단됨

    -> setTimeout 종료되고, 함수가 끝나서 콜스택이 빔

    -> setTimeout에 인자로 넣어뒀던 함수가 실행되면서 isWaiting = false이 설정됨

    -> 다시 실행 가능 상태

     

     

    _.throttle과 비교해보니 다른 점이 눈에 띈다.

    lodash의 throttle은 Control의 9th, 18th 턴에서 실행되었는데, 내가 구현한 함수는 10th에서만 실행되었다.

    아마도 내 함수에는 제한시간 내에 들어온 요청을 모아뒀다가 제한이 해제되면 실행하는 로직이 없어서인 듯하다.

    지금은 제한시간 동안 들어온 요청은 그냥 무시해버리기 때문에, 다음 호출 때에 실행되어서 한번씩 밀리는 듯.

     

    이렇게 함수를 실행해주는 함수를 어떻게 테스트하는가를 고민해보았다.

     

    전체 코드

    -파이어폭스 개발자도구 콘솔에서 실행해서 IIFE로 감쌌다

    (function() {
      const throttle = function (func, wait) {
        let isWaiting = false;
    
        return function () {
          if (isWaiting) {
            return;
          }
    
          setTimeout(() => {
            isWaiting = false;
          }, wait);
    
          isWaiting = true;
          return func();
        };
      };
      
      const showCallingSequence = function (name = '') {
        let nthRun = 0;
        return function () {
          const currentTime = Date.now();
          console.log(`[${name}] ${nthRun++}th ${currentTime}`);
        };
      }
      
      const experimental = showCallingSequence('■Throttle');
      const throttled = throttle(experimental, 500); // throttle 기준이 500이므로 0.5초에 1번만 실행된다
    
      const control = showCallingSequence('□Control');
      
      const timeID = setInterval(() => {
        control();
        throttled();
      }, 50);
      setTimeout(() => clearInterval(timeID), 1000);
    })();

    'FrontEnd > JavsScript' 카테고리의 다른 글

    React in JS Bin  (0) 2022.03.02
    비동기 흐름 정리 feat. Element.animate()  (0) 2021.05.03
    SVG로 Progress-bar 만들기 with JS로 width 변경  (0) 2021.03.23

    댓글

Designed by Tistory.