(javascript) Throttle, Debounce를 통한 따닥 방지
1. Throttle, Debounce 도입 배경
운영 중인 시스템에서 특정 기능을 사용할 때 '따닥' 현상으로 인해 API 요청이 중복으로 호출되는 문제가 종종 발생했는데요.
빈도가 높지는 않았지만 발생할 때마가 데이터가 꼬이는 문제가 생겼기 때문에 어떻게든 처리가 필요한 상황이었습니다.
물론 서버 측에서 동일한 키 값에 대한 API 요청이 중복으로 일어나지 않도록 처리되어 있었지만, 트랜잭션이 끝나기 전 동일한 요청이 들어오는 경우 이러한 문제가 발생하였으며, 원인은 스페이스 키 또는 엔터 키의 연타로 인해 발생하는 것으로 예상되었습니다.
(스페이스 키, 엔터 키를 막는 방법은 사용자의 편의성으로 인해 적용할 수 없는 상황이었습니다.)
따닥 문제를 해결하기 위해 먼저 고려한 방안으로는 Redis를 활용한 뮤텍스 락(Mutex Lock)이 있었지만, 내부 시스템이라 사용량 대비 적용 절차나 작업량을 고려하였을 때 좋은 선택지는 아니라고 판단되었습니다.
그러던 중 javascript 단에서 따닥 현상을 막기 위해 비교적 간단하게 적용할 수 있는 throttle, debounce라는 개념을 알게 되었는데요.
프로그래밍 기법 중 하나인 스로틀(throttle)과 디바운스(debounce)는 이벤트를 제어하는 방법으로, 자주 호출되는 이벤트의 실행 빈도를 조절하여 과도한 함수 호출을 방지하고 성능상의 이점을 얻기 위해 사용되는 방식입니다.
해당 포스팅에서는 각각의 기법에 대한 개념 및 동작 방식, 구현 코드를 살펴보도록 하겠습니다.
2. Throttle 개념 및 소스 코드
Throttle 기법은 주어진 시간 간격(delay)마다 함수가 한 번만 호출되도록 하는 방식입니다.
즉, 이벤트 발생 후 일정 시간이 지나지 않으면 동일한 이벤트가 발생해도 그 이벤트에 대한 함수 실행을 막습니다.
(스로틀 방식은 이벤트가 발생하는 경우 일정 시간 안에 한 번은 실행된다는 보장이 있다는 특징이 있습니다.)
예를 들어 스크롤 이벤트에서 스크롤로 인식되는 모든 픽셀에 대해 함수가 실행되는 것은 리소스적으로 비효율적일 수 있습니다.
따라서 스크롤마다 함수가 실행되는 대신 일정 시간 간격으로만 함수가 실행되도록 제한하는 방식으로 적용될 수 있습니다.
// throttle 호출 시 timeout 값이 넘어오지 않는 경우 300을 사용, 넘어오는 경우 넘어오는 값을 사용 (timeout의 경우 ms 단위)
function throttle(func, timeout = 300) {
let waiting = false;
return(...args) => {
if(!waiting) {
func.apply(this, args);
waiting = true;
setTimeout(() => {
waiting = false;
}, timeout);
}
}
}
// throttle로 감싼 target 이벤트 처리 함수
const throttledTargetEventHandler = throttle(targetEvent, 1000); // 1000ms(1초) 간격으로 이벤트 처리
throttle 구현 소스코드를 보면 다음과 같습니다.
핵심은 waiting이라는 변수를 통해 함수 실행을 제어한다는 것입니다.
waiting 변수가 'false'인 경우에만 함수가 실행되며, 함수 실행 후 변수 값을 'true'로 변경하여 이후 요청되는 이벤트에 대해 함수가 실행되지 않도록 합니다.
그리고 timeout 시간이 지난 뒤 setTimeout() 함수가 동작하면서 waiting 변수 값을 다시 'false'로 바꿔 이후 이벤트에 대한 함수가 실행될 수 있도록 하는 원리입니다.
3. Debounce 개념 및 소스 코드
기본적인 Debounce 기법은 연속으로 발생하는 이벤트에 대해 마지막 이벤트가 끝난 후 일정 시간(delay) 동안 이벤트가 더 이상 발생하지 않을 때 함수를 실행합니다.
이러한 방식을 'Triling Edge 방식'이라고 합니다.
또 하나의 방식은 'Leading Edge 방식'인데요.
이 방식은 이벤트가 처음 발생했을 때 함수를 실행하며, 이후 일정 시간(delay) 동안 이벤트가 더 이상 발생하지 않은 후 발생하는 이벤트에 대한 함수를 다시 실행하는 방식입니다.
앞선 Throttle 기법과의 차이점으로는 Throttle의 경우 이벤트가 발생하는 경우 일정 시간 안에 한 번은 실행된다는 보장이 있었는데요.
Debounce의 경우 일정 시간(delay) 안에 이벤트가 계속 발생하는 경우 맨 마지막 이벤트에 대한 함수만 실행되거나, 맨 처음 이벤트에 대한 함수만 실행된다는 큰 차이점이 있습니다.
// Trailing Edge 구현 코드
function debounceTrailingEdge(func, timeout = 300) {
let timer;
return(...args) => {
clearTimeout(timer);
timer = setTimeout(() => {
func.apply(this, args);
}, timeout);
}
}
// Leading Edge 구현 코드
function debounceLeadingEdge(func, timeout = 300) {
let timer;
return(...args) => {
if (!timer) {
func.apply(this, args);
}
clearTimeout(timer);
timer = setTimeout(() => {
timer = undefined;
}, timeout);
}
}
debounce 구현 코드를 보면 다음과 같습니다.
debounce의 핵심은 timer 변수와 clearTimeout() 함수인데요.
clearTimeout() 함수는 이전에 설정된 timer를 취소하는 기능을 하며, 이를 통해 이벤트가 연속적으로 발생할 때마다 이전의 타이머를 취소하고 새로운 타이머를 설정하여 불필요한 호출을 막는 역할을 수행합니다.
trailing edge 방식의 경우 이벤트가 더 이상 발생하지 않아 setTime() 함수가 동작할 때 대상 함수가 실행됩니다.
반면, leading edge 방식의 경우 맨 처음 이벤트가 들어왔을 때 대상 함수가 실행되고, 이후 이벤트가 더 이상 발생하지 않아 setTimeout() 함수가 동작하면서 timer 변수를 undefined 상태로 만들어 이후 이벤트에 대한 함수가 실행될 수 있도록 동작됩니다.
'Programming > Javascript' 카테고리의 다른 글
javascript 배열 비교, 차집합 교집합 구하는 방법 (0) | 2024.12.10 |
---|---|
javascript 유효성 검사 함수 test(), exec(), match() 비교 정리 (0) | 2024.10.23 |
(Javascript) a 태그의 download 속성을 통한 파일 다운로드 방법 및 예시 (1) | 2024.07.14 |
JavaScript 이벤트 전파 개념과 이벤트 전파 막는 방법 (0) | 2024.04.03 |
(javascript) input image width, height 이미지 너비, 높이 구하기 (0) | 2023.01.19 |