ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Javascript] 쓰로틀링, 디바운싱(throttling, debouncing)?
    Front-end/Javascript 2020. 7. 26. 19:29
    반응형

    ++ 광고 클릭은 블로그를 계속 운영할 수 있는 꿈과 희망 그리고 사랑이 됩니다...

     

    잠깐, 들어가기 전에

    웹 개발을 해보았다면, 아마도 반응형 웹페이지를 만들어본 적이 있을 것이다.

    해보았다고 아주아주아주 굳게 믿고있다.

     

    왼쪽 Navigation bar를 만들면 크게 문제가 되지 않지만, 오른쪽 Navigation bar을 만들 때 이슈가 종종 생긴다.

    보통의 사용자 경험상, 사람들은 오른쪽을 기준해 윈도우를 리사이징한다. 이때, 아래 코드처럼 윈도우 리사이즈 이벤트를 사용중이라면 슬슬 퍼포먼스 이슈가 생긴다.

    function customFunction() {
      // logic
      console.log('write logic what you wanna do')
    }
    window.addEventListener('resize', customFunction);

     

    아주아주아주 작은 픽셀의 변화에도 이벤트 리스너에 등록된 customFunction 함수가 실행된다.

    이게 만약 api 호출 또는 DOM 조작이라면...? 생각만해도 끔찍할만큼 퍼포먼스 이슈 뿜뿜이다.

    대표적인 사례로, Twitter에서 이것으로 인한 퍼포먼스 향상을 위해 생각한 개념이 쓰로틀링과 디바운싱이라고 한다.


    디바운싱(Debouncing)

     

    JavaScript에서 제거는 브라우저 성능을 향상시키는 데 사용되는 방법이다. 웹 페이지에는 시간이 많이 걸리는 계산이 필요한 일부 기능이있을 수 있다. JavaScript가 단일 스레드 언어이므로 이러한 메소드를 자주 호출하면 브라우저 성능에 큰 영향을 줄 수 있습니다. Debouncing은 시간이 많이 걸리는 작업이 자주 발생하지 않도록해서 웹 페이지의 성능이 저하되도록하는 프로그래밍 방식이라고 할 수 있다. 즉, 함수 호출 속도를 제한한다.

     

    즉, 특정 시간(인터벌) 내에 이벤트 발생이 없으면 로직이 실행된다. 

    i.e) 0.5초동안 input 이벤트가 발생하지 않으면 api call 실행 !

     

    - 혼돈의 시작

    <html>  
    <body> 
    <input placeholder="Enter some text" name="name"/>
    <p id="values"></p>
    
    <script> 
    // Getting dom from each attribute
    const input = document.querySelector('input');
    const log = document.getElementById('values');
    
    input.addEventListener('input', () => {
        log.textContent = e.target.value 
        {
          // suppose ajax call logic here
         }
    });                        
    </script> 
    
    </body> 
    </html> 

    위의 코드를 참고해보자.

    User View에는 input과 p 태그가 보이고, 유저가 타이핑을 할 때마다, p 태그에 입력값이 변화할 것이다.

    만약 서치엔진에서 Backend에 api 콜을 보내야한다고 가정해보자.

    그게 aws고, 다른 유료 api를 쓴다면...? 많은 유저가 사용하는 커뮤니티라면, 월말에 폭탄이 담긴 bill을 받을 것이다.

    그리고 DOM을 다시 렌더링 하는 비용도 만만치 않아서, 유저 경험상에서의 웹페이지도 아주 버벅버벅버벅할 것은 해보지 않아도 눈에 선하다. 어떻게 해결 할 수 있을까?

     

    - 솔루션

    <html>  
    <body> 
    <input placeholder="Enter some text" name="name"/>
    <p id="values"></p>
    
    <script> 
    // Getting dom from each attribute
    const input = document.querySelector('input');
    const log = document.getElementById('values');
    var debounceTimer
    
    input.addEventListener('input', () => {
        if (debounceTimer) clearTimeout(debounceTimer)
          debounceTimer = setTimeout(() => {
          // (1)
          log.textContent = e.target.value 
        }, 500) 
    });                        
    </script> 
    
    </body> 
    </html> 

    디바운싱을 통해서 쉽게 해결 할 수 있다.

    주로 사람들은 검색을 할 때는, 빠른 시간 내에 단어를 완성한다. 따라서, 유저가 0.5초동안 입력하지 않으면, 키워드 입력이 끝났다고 가정하자. 유저가 쭉쭉 연속적으로 입력을 하다가 0.5초동안 입력이 없을 때, 비로소 p 태그에 이를 보여주고, ajax 또는 cost consuming하는 로직을 실행할 것이다.

    코드를 살펴보면 인풋 태그에 입력 이벤트가 발생하면, 기존에 있던 타이머를 clear하고 0.5초짜리 setTimeout을 다시 설정한다. 따라서, 0.5초 내에 이벤트가 발생하지 않을 때, 마지막 이벤트에 (1)의 로직을 실행한다.

     


    쓰로틀링(Throttling)

     

    쓰로틀링은 매 밀리 초마다 또는 특정 시간 간격 후에 첫 번째 클릭만 즉시 실행되는 함수를 호출하는 데 사용된다. 디바운싱과 함께 퍼포먼스 이슈를 해결하기 위한 방법 중 하나이지만, 차이점이 있다.

    디바운싱: 특정 시간동안 이벤트가 발생하지 않으면, 비지니스 로직을 발생시킨다.

    쓰로틀링: 특정 시간동안의 이벤트는 모두 무시.

     

    - 혼돈의 시작

    <html>  
    <body> 
    <input placeholder="Enter some text" name="name"/>
    <p id="values"></p>
    
    <script> 
    // Getting dom from each attribute
    const input = document.querySelector('input');
    const log = document.getElementById('values');
    
    input.addEventListener('input', () => {
        log.textContent = e.target.value 
        {
          // suppose ajax call logic here
         }
    });                        
    </script> 
    
    </body> 
    </html> 

    디바운싱에서의 예시와 같이 위의 코드는 문자를 입력할 때마다 이벤트가 발생한다.

    하지만, 이것을 쓰로틀링의 관점에서 퍼포먼스 degredation을 피할 수 있다.

     

    - 솔루션

    <html>  
    <body> 
    <input placeholder="Enter some text" name="name"/>
    <p id="values"></p>
    
    <script> 
    // Getting dom from each attribute
    const input = document.querySelector('input');
    const log = document.getElementById('values');
    var throttiingTimer
    
    input.addEventListener('input', () => {
        if (!throttiingTimer) {
          // (1)
          throttiingTimer = setTimeout(() => { log.textContent = e.target.value }, 500) 
        }
    });                        
    </script> 
    
    </body> 
    </html> 

    디바운싱과의 차이를 요리조리 다시 찾아보자.

    디바운싱은 0.5초동안 입력이 연속된다면, 입력시간 기준으로 timer는 계속 늦춰진다.

     

    쓰로틀링의 코드를 보면, 기존의 throttlingTimer가 존재하면 아무 로직도 실행하지 않는다. 

    하지만, 없으면 0.5초 후에 로직을 실행하는 setTimeout을 설정한다.

    쓰로틀링은 이벤트리스너가 반응해도 최소한은 특정 시간동안 // (1)부분의 로직을 주기적으로 1회만 실행한다.

     


    마무리

     

    디바운싱과 쓰로틀링은 사용자 스크롤(scroll)이나 윈도우 리사이징(window resizing)에 이벤트 리스너를 사용해서 무언가를 해야할 때, 끊임없는 로직을 실행을 피하기 위해서 사용하는 기법이다.

    첫 번째로, 단일스레드인 자바스크립트 퍼포먼스 degredation을 피하기 위해서

    두 번째로, api call과 같은 cost와 관련된 로직이 있을 때, cost saving을 하기 위해서

    두 기법을 고려하게 된다.

     

    최근 input 태그에서 입력한 내용들을 오른쪽의 div창에서 scroll 위치를 sync하기 위해서 위 기법을 고려하게 되었다.

     

    ++

    Lodash를 사용할 경우, 아래와 같이 따로 구현하지 않고 편하게 사용할 수 있다.

    _.debounce(customFunction, 500)
    _.throttle(customFunction, 500)

    참고

    GeeksforGeeks: https://www.geeksforgeeks.org/debouncing-in-javascript/
    제로초님 블로그: https://www.zerocho.com/category/JavaScript/post/59a8e9cb15ac0000182794fa
    쓸데없는 코딩하기님 블로그: http://sub0709.tistory.com/52 
    KimJaehee님 블로그: webclub.tistory.com/607
    반응형

    댓글

Designed by Tistory.