안녕하세요. 이번 시간에는 쓰로틀링(throttling)과 디바운싱(debouncing)에 대해 알아보겠습니다. 원래 예정에 없던 강좌이지만 요청을 받았기 때문에 써봅니다. 프로그래밍 기법 중 하나입니다(아니, 둘 인가요...).
용어가 생소하신 분들을 위해 간단히 설명해보겠습니다.
- 쓰로틀링: 마지막 함수가 호출된 후 일정 시간이 지나기 전에 다시 호출되지 않도록 하는 것
- 디바운싱: 연이어 호출되는 함수들 중 마지막 함수(또는 제일 처음)만 호출하도록 하는 것
위 두 개는 underscore(_)에도 있는 기능입니다. underscore나 lodash를 쓰고 계신 분들이라면 그 라이브러리의 메소드를 쓰시면 편합니다.
사용처가 궁금하실 텐데요. 디바운싱은 주로 ajax 검색에 자주 쓰입니다. 쓰로틀링은 스크롤을 올리거나 내릴 때 보통 사용합니다. 어디까지나 제 경험에 바탕한 사용처입니다.
디바운싱
요즘 서비스들은 검색어 치자마자 엔터 없이도 결과가 바로바로 나오죠? 만약 '제로초'를 검색창에 친다고 합시다. 엔터 없이도 결과를 즉시 보여주려면 항상 input 이벤트에 대기하고 있어야 합니다.
<input id="input" />
document.querySelector('#input').addEventListener('input', function(e) {
console.log('여기에 ajax 요청', e.target.value);
});
실제 ajax 요청을 보내기는 힘드니 콘솔 로그로 대체합니다. 로그가 콘솔에 찍힐 때마다 ajax 요청이 실행된다고 생각하시면
됩니다. 문제는 한 글자 칠 때마다 ajax 요청이 실행된다는 것입니다. 'ㅈ', '제', '젤', '제로', '제롳', '제로초' 모두 요청이 실행됩니다. 6번이나 요청을 했습니다(한글같은 조합형 언어는 사진처럼 6번보다 더 많이 이벤트가 발생할 수도 있습니다). 거기에 'ㅈ', '젤', '제롳'는 제대로 된 검색 결과가 나오지 않을 것 같은 검색어입니다.
이와 같은 낭비는 유료 API를 사용했을 때 큰 문제가 됩니다. 만약 구글지도 API같은 것을 사용할 때 위와 같이 쿼리를 10번 날리면 어마어마한 손해입니다. 쿼리 하나가 다 돈이거든요. 따라서 디바운싱은 비용적인 문제와도 관련이 있습니다. 우리는 마지막 '제로초'를 다 쳤을 때 ajax 요청을 보내고 싶습니다.
먼저 어떻게 구현할지 생각해봅시다. 보통 사람들은 타자를 연달아칩니다. 중간에 잠시 생각하느라 몇 초 쉴 수는 있겠지만 대부분 한번에 검색어를 입력합니다. 따라서 입력이 다 끝난 후에 요청을 보내면 됩니다. 즉 타자를 칠 때(input 이벤트 발생)마다 타이머를 설정합니다. 200ms동안 입력이 없으면 입력이 끝난 것으로 칩니다(시간은 적당히 설정하면 됩니다). 200ms 이전에 타자 입력이 발생하면 이전 타이머는 취소하고 새로운 타이머를 다시 설정하는 겁니다.
var timer;
document.querySelector('#input').addEventListener('input', function(e) {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(function() {
console.log('여기에 ajax 요청', e.target.value);
}, 200);
});
이제 더는 여러 번 호출되지 않습니다. 이게 바로 디바운싱입니다. 한글 특성상 마지막에 두 번 호출되는 경우도 있습니다.
쓰로틀링
쓰로틀링은 보통 성능 문제 때문에 많이 사용합니다. 특성 자체가 실행 횟수에 제한을 거는 것이기도 하고요.
스크롤을 올리거나 내릴 때 scroll 이벤트가 매우 많이 발생합니다. scroll 이벤트가 발생할 때 뭔가 복잡한 작업을 하도록 설정했다면 매우 빈번하게 실행되기 때문에 엄청 렉이 걸릴 것입니다. 그럴 때 쓰로틀링을 걸어줍니다. 몇 초에 한 번, 또는 몇 밀리초에 한 번씩만 실행되게 제한을 두는 것이죠.
디바운싱으로 구현했던 ajax 검색을 쓰로틀링으로 대체해도 됩니다. 물론 쿼리는 조금 더 날리겠지만요. 한 번 구현해보겠습니다. 똑같이 200ms초 제한을 걸어두었습니다. 타이머가 설정되어 있으면 아무 동작도 하지 않고, 타이머가 없다면 타이머를 설정합니다. 타이머는 일정 시간 후에 스스로를 해제하고, ajax 요청을 날리게 하면 됩니다.
var timer;
document.querySelector('#input').addEventListener('input', function (e) {
if (!timer) {
timer = setTimeout(function() {
timer = null;
console.log('여기에 ajax 요청', e.target.value);
}, 200);
}
});
이제 최소 200밀리초마다 요청을 보냅니다. 물론 ajax 검색은 디바운싱으로 처리하는 게 더 나아보입니다. 하지만 중간 중간 검색 결과도 보여주고 싶다면 쓰로틀링도 괜찮은 옵션인 것 같습니다.
물론 매우 빠르게 구현한 것이기 때문에 이 코드로는 예외 사항들을 처리하지 못 할 수도 있습니다. underscore의 _.debounce
와 _.throttle
을 추천합니다.
이렇게 디바운싱과 쓰로틀링에 대해 알아보았습니다. 코드는 잊어버리셔도 좋습니다. 하지만 용어는 기억해두세요. 그래야 나중에 다시 검색할 수 있습니다.
리덕스 사가에 아예 debounce라는 이펙트가 있습니다.