Lsiron
AJAX와 fetch 그리고 디바운싱, 쓰로틀링, lodash 본문
1. AJAX
AJAX (Asynchronous Javascript And XML)란, JavaScript의 라이브러리중 하나이며 비동기식 자바스크립트와 xml 의 약자이다. 브라우저가 가지고있는 XMLHttpRequest 객체를 이용해서 전체 페이지를 새로 고치지 않고도
페이지의 일부만을 위한 데이터를 로드하는 기법 이며, JavaScript를 사용한 비동기 통신, 클라이언트와 서버간에 XML 데이터를 주고받는 기술이다.
즉, 쉽게 말하자면 자바스크립트를 통해서 서버에 데이터를 요청하는 것이다. 종류는 GET, POST, PUT, DELETE 가 있다.
2. fetch
fetch 매서드란, JavaScript에서 서버로 네트워크 요청을 보내고 응답을 받을 수 있도록 해주는 매서드이다.
즉, fetch 함수는 네트워크 요청을 보내고 응답을 받기 위한 Web API이며, Promise를 반환한다. fetch 함수를 사용하면 서버로부터 데이터를 가져오는 네트워크 요청을 보낼 수 있다. 이때 fetch 함수는 해당 요청에 대한 응답을 나타내는 Response 객체를 Promise로 반환한다.
fetch('https://Lsiron.tistory.com')
.then(response => response.json())
.then(data => {
console.log(data); // 응답 데이터 확인
})
.catch(error => {
console.error('Error:', error);
});
=> 위 코드에서 fetch 함수는 'https://Lsiron.tistory.com' URL로 GET 요청을 보내고,
서버로부터 받은 응답 데이터를 JSON 형식으로 변환하여 처리한다. 이후 then 메서드를 사용하여 응답 데이터를 출력하거나 다른 처리를 할 수 있다. 만약 에러가 발생할 경우 catch 메서드를 사용하여 에러를 처리할 수 있다.
기본적인 구조와 동작은 Promise 객체와 동일하다. 파라미터로 요청을 보낼 url을 입력해주고 응답을 받아서 추가적인 작업 또한 해줄 수 있다. url이 아닌, 파일명이 될 수도 있다. ex) data.json
fetch(url, {
method: 'post',
headers: {
"Content-type": "application/x-www-form-urlencoded; charset=UTF-8"
},
body: 'foo=bar&lorem=ipsum'
})
.then(res => {
console.log(res);
})
.catch(error => console.log(error));
=> fetch의 두 번째 파라미터로 요청에 대한 추가적인 데이터를 입력할 수 있다.
즉, 첫 번째 파라미터에는 요청할 곳(url, 파일명 등등), 두 번째 파라미터에는 요청에 대한 추가적인 데이터 입력.
method : HTTP method와 동일하며 요청 방식을 나타낸다. (GET, POST, PUT, DELETE 등)
headers : 요청 헤더에 대한 정보를 나타낸다.
body : 요청을 보내는 데이터를 나타낸다. 여러 가지 자료형을 대입할 수 있다.
let obj = {
method: 'post',
headers: {
"Content-type": "application/x-www-form-urlencoded; charset=UTF-8"
},
body: 'foo=bar&lorem=ipsum'
}
fetch(url, obj)
.then(...)
=> fetch 파라미터로 직접 입력하기도 하지만 주로 객체 변수에 저장해서 대입하는 방식으로도 사용한다.
fetch(url, {
method: 'POST',
body: JSON.stringify({ name: "hello!" })
})
.then(res => {
if (res.status === 200) { //여기서 200이란 http 코드인데, 요청이 성공했음을 나타내는 성공응답 상태코드이다!
res.text().then(text => console.log(text)
}
else {
console.log(res.statusText)
}
})
.catch(err => console.log(err))
=> POST로 body안에 데이터를 넣고, 요청을 보내주면 응답 객체 res를 받게 되는데 res 안에는 응답에 관한 정보가 존재한다. status는 요청이 성공인지 실패인지를 판별할 수 있게 해주는 요소이다.
또 응답에 대한 내용은 res.text()를 통해 확인할 수 있으며, text() 외에도 arrayBuffer, blob, json, formData 등의 메서드를 사용하여 값을 볼 수도 있다. GET, PUT, DELETE 요청도 같은 방식으로 보낼 수 있지만 GET, DELETE 요청은 url 파라미터 하나만 입력하여 사용한다.
http 상태코드 => https://ko.wikipedia.org/wiki/HTTP_%EC%83%81%ED%83%9C_%EC%BD%94%EB%93%9C
허나, res.status === 200 대신, res.status >= 200 && res.status < 300 으로 범위를 직접 확인하는 방법이 있다. 예를 들어, 성공 응답 상태 코드가 200에서 299 사이에 있는지를 확인할 수 있는 것이다.
성공 응답 상태 코드를 확인하는 또 다른 방법으로는 res.ok 를 사용할 수 있다. res.ok는 응답이 성공적으로 이루어졌는지를 나타내는 불리언 값으로, 응답 상태 코드가 200에서 299 사이인 경우 true를 반환한다. 따라서 res.ok를 사용하여 응답 상태를 확인할 수 있다.
즉, ( res.status === 200 ) = ( res.status >= 200 && res.status < 300) = ( res.ok ) 셋 다 사용가능하다.
다음은 Elice에서 문제를 풀다가 생긴 의문이다. 왜 fetch로 호출하고 굳이 변수를 하나 더 선언하는거지?
async function show() {
const res = await fetch(url);
const data = await res.json();
}
=> fetch로 호출을 할 때, 위와 같은 형식으로 가져오게 되는데, fetch 함수로 API를 호출한 후에는 서버로부터 받은 응답(Response)을 JSON 형식으로 변환하기 위해 res.json() 메소드를 사용하는 것이다.
res.json() 메소드를 사용하면 JSON 형식의 데이터를 자바스크립트 객체로 변환하여 변수에 저장할 수 있다. 그래서 const data = await res.json(); 라인은 API 응답을 JSON 형식으로 변환하여 사용하기 쉽게 만든 것이다.
즉, 쉽게 말해서 const res 는 fetch를 사용하여 API를 호출 한 것이고, const data는 호출한 API를 json 데이터 형식으로 받아와서 쓰려고 만든것이다! = 호출만 해선 쓸 수가 없으니 json 데이터로 쓰기위해 변환한것.
참고) API는 음식점으로 따지면 홀서빙직원이라고 생각하면 된다.
손님(클라이언트)이 홀서빙 직원(API)에 주문을 하면 주방장(서버)이 처리해서 홀서빙 직원(API)은 음식을 다시 손님(클라이언트)에게 가져다 주는것.
3. 디바운싱(검색 이벤트에 주로 사용)
먼저, 디바운싱과 쓰로틀링 이 두 가지 방법 모두 DOM 이벤트를 기반으로 실행하는 자바스크립트를 성능상의 이유로 JS의 양적인 측면, 즉 이벤트(event)를 제어(제한)하는 프로그래밍 기법이다.
웹/앱 사용자가 스크롤(scroll wheel), 트릭 패드, 스크롤 막대를 드레깅 한다고 하면, 사용자는 크게 느끼지 못할 수 있으나 이 행위로 인해 수많은 스크롤 이벤트가 발생하게 된다.
이때 매번 스크롤 이벤트에 대한 콜백(callback)이 발생하고 그 콜백이 수행하는 일은 매우 큰 리소스를 잡아먹게 될 것이다.
디바운싱과 쓰로틀링은 이벤트가 과도한 횟수로 발생하여 이벤트 핸들러가 무거운 연산을 수 없이 많이 수 행하는 경우에 제약을 걸어 제어할 수 있는 수준으로 이벤트를 발생시키는 것을 목표로 하는 기술이다.
디바운싱은 연이어 발생하는 이벤트 중 마지막 이벤트를 기다렸다가 한 번만 처리하는 방식이다.
요즘 서비스들은 검색어 치자마자 엔터 없이도 결과가 바로바로 나온다. 만약 'test'를 검색창에 친다고 하자. 엔터 없이도 결과를 즉시 보여주려면 항상 input 이벤트에 대기하고 있어야 한다.
<input id="input" />
document.querySelector('#input').addEventListener('input', function(e) {
console.log('여기에 ajax 요청', e.target.value);
});
=> 로그가 콘솔에 찍힐 때마다 ajax 요청이 실행된다고 생각하면 되겠다.
문제는 한 글자 칠 때마다 ajax 요청이 실행된다는 것이다. 't', 'te', 'tes', 'test' 모두 요청이 실행된다. 4번이나 요청을 했다(한글같은 조합형 언어는 더 많은 이벤트가 발생할 것이다).
이와 같은 낭비는 유료 API를 사용했을 때 큰 문제가 된다. 만약 구글지도 API 같은 것을 사용할 때 위와 같이 쿼리를 10번 날리면 어마어마한 손해이다. 따라서 디바운싱은 비용적인 문제와도 관련이 있다. 마지막 test를 다 쳤을 때 ajax 요청을 보내보자.
보통 타자를 연달아 치기 때문에 입력이 다 끝난 후에 요청을 보내면 되겠다. 타자를 칠 때(input 이벤트 발생)마다 타이머를 설정한다. 200ms동안 입력이 없으면 입력이 끝난 것으로 친다(시간은 자율적으로 설정하면 된다). 200ms 이전에 타자 입력이 발생하면 이전 타이머는 취소하고 새로운 타이머를 다시 설정하는 것이다.
var timer;
document.querySelector('#input').addEventListener('input', function(e) {
// 아직 타이머 수행되지 않았으면(직전 입력으로부터 200ms가 지나지 않았다면) 타이머 취소
// (타이머가 존재하면, 즉 직전 입력에서 타이머를 생성했다면)
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(function() {
console.log('여기에 ajax 요청', e.target.value);
}, 200);
});
=>여기서 clearTimeout()은 setTimeout()가 반환한 타이머 ID를 이용해 타이머를 취소 할 수 있다.
if문을 사용하여 timer를 사용하지 않으면 함수가 호출될 때마다 새로운 타이머가 설정되지 않고, 입력이 있을 때마다 즉시 AJAX 요청이 발생할 수 있지만, timer를 사용하면, 입력이 발생했을때 일정 시간동안 다른 입력을 받지 않고 기다린 후에 AJAX 요청을 보내기 때문에 효율적인 요청 관리를 할 수 있다.
즉, 이 코드는 timer가 존재하는 경우에만 clearTimeout을 사용하여 이전 타이머를 초기화하고, 새로운 타이머를 설정한다.
이렇게 함으로써 마지막 이벤트가 발생한 후에만 AJAX 요청이 발생하도록 보장한다.
쉽게말해서,
내가 구글에 사과를 검색할때 밑에 연관검색어가 나오는데 timer 설정이 없으면 'ㅅ' 입력했을때 연관검색어 '사' 입력했을때 연관검색어 '사ㄱ' 입력했을때 연관검색어 '사과' 입력했을때 각 개소마다 일정시간이 다 부여돼서 시간이 끝나는 순서대로 연관검색어가 연쇄적으로 좌라락 뜬다는것이다.
허나 디바운싱 설정으로 인해, 새로운 문자를 입력할 때마다 이 전 문자에 부여된 시간을 매번 초기화(clearTimeout) 시킨다. '사과'를 다 입력해 놓고 일정시간(여기서 200ms)이 지나면 마지막 이벤트인 '사과'에 대한 연관검색어만 뜨는 것이다.
4. 쓰로틀링(스크롤 이벤트에 주로 사용)
쓰로틀링은 보통 성능 문제에 많이 사용하며, 일정 주기마다 이벤트를 처리하는 방식을 뜻 한다. 즉, delay 시간 동안 호출 된 함수는 무시하는 케이스이다.
스크롤을 올리거나 내릴 때 scroll 이벤트가 매우 많이 발생한다. scroll 이벤트가 발생할 때 뭔가 복잡한 작업을 하도록 설정했다면 매우 빈번하게 실행되기 때문에 엄청 렉이 걸릴 것이다. 그럴 때 쓰로틀링을 걸어주는 것이다. 몇 초에 한 번, 또는 몇 밀리초에 한 번씩만 실행되게 제한을 두는 것이다.
위와 같이 200ms 제한을 걸어두었다. 타이머가 설정되어 있으면 아무 동작도 하지 않고, 타이머가 없다면 타이머를 설정한다. 타이머는 일정 시간 후에 스스로를 해제하고, ajax 요청을 날리게 하면 된다.
var timer;
document.querySelector('#input').addEventListener('input', function (e) {
// 타이머가 없을 때만 타이머 설정.
// 만약 200ms가 지나서 해당 함수를 실행하면 타이머는 사라진다. (timer = null)
// 만약 타이머 설정 후 200ms가 지나지 않았다면 아무 일도 일어나지 않는다.
// 따라서 최소 200ms 마다 한번씩만 아래의 코드가 실행된다.
if (!timer) {
timer = setTimeout(function() {
timer = null;
console.log('여기에 ajax 요청', e.target.value);
}, 200);
}
});
=> if문을 사용하여 timer를 사용하지 않으면 사용자가 입력을 할 때마다 계속해서 타이머가 설정될 수 있지만, timer를 사용하면 설정한 시간(여기서는 200ms)이 지나야 새로운 타이머가 설정된다. 따라서 사용자가 입력을 빠르게 하더라도 일정 주기(200ms)마다만 AJAX 요청이 발생하도록 제어할 수 있다.
즉, 이 코드는 timer가 존재하지 않는 경우에만 setTimeout을 사용하여 일정 주기(여기서는 200ms)마다 AJAX 요청을 보내도록 설정한다. timer를 초기화한 후에는 다음 이벤트까지 요청을 보내지 않는다.
쉽게말해서, 스크롤을 올리거나 내릴 때 scroll 이벤트가 매우 많이 발생한다. scroll 이벤트가 발생할 때 뭔가 복잡한 작업을 하도록 설정했다면 매우 빈번하게 실행되기 때문에 엄청 렉이 걸릴 것이다. 그럴 때 걸어주는 것이 바로 쓰로틀링이다. 몇 초에 한 번, 또는 몇 밀리초에 한 번씩만 실행되게 제한을 두는 것이다.
5. lodash
lodash에서는 위에서 설명한 디바운싱과 쓰로틀링을 쉽게 적용할 수 있도록 debounce, throttle 메소드를 제공한다.
아래와 같이 디바운싱 혹은 쓰로틀링을 적용할 함수를 메소드로 감싸고 2번째 인자로 delay 값을 전달하면 된다.
디바운싱
import { debounce } from 'lodash';
const DebounceInput = () => {
const handleChange = debounce(({ target }) => {
console.log('debounce', target.value);
}, 500);
return <input type="text" onChange={handleChange} />;
};
export default DebounceInput;
쓰로틀링
import { throttle } from 'lodash';
const ThrottleInput = () => {
const handleChange = throttle(({ target }) => {
console.log('throttle', target.value);
}, 500);
return <input type="text" onChange={handleChange} />;
};
export default ThrottleInput;
현업에서 디바운싱과 쓰로틀링은 둘 다 많이 사용된다.
특히 웹 애플리케이션 개발에서 사용자 입력에 따른 이벤트 처리나 API 호출 등에서 디바운싱과 쓰로틀링을 적용하여 성능을 최적화하고 사용자 경험을 향상시키는데 활용된다.
디바운싱은 사용자의 연이은 입력에 대한 반복적인 작업을 방지하여 네트워크 부하를 줄이고, 불필요한 작업을 방지하는 데 유용하다.
예를 들어, 검색창 자동완성 기능이나 스크롤 이벤트에 대한 작업을 디바운싱을 적용하여 부드러운 사용자 경험을 제공할 수 있다.
쓰로틀링은 일정 주기마다 이벤트를 발생시키는 것으로, 스크롤 이벤트나 리사이징 이벤트와 같이 빈번하게 발생하는 이벤트에 대해 일정 주기로 작업을 처리하는 데 유용하다.
이를 통해 불필요한 작업을 줄이고 성능을 최적화할 수 있습니다.
따라서, 디바운싱과 쓰로틀링은 현업에서 웹 개발 및 프론트엔드 개발에서 많이 사용되며, 성능 최적화와 사용자 경험 향상에 큰 도움을 줍니다.
출처: https://hengxi.tistory.com/21 ==>디바운싱과 쓰로틀링
https://yunamom.tistory.com/98#google_vignette ==>ajax 요청
https://ljtaek2.tistory.com/130 ==>fetch란?
'언어 > Java Script' 카테고리의 다른 글
map() 과 forEach()의 차이점, rendering 이란? (0) | 2024.05.18 |
---|---|
Callback, promise, async / await (0) | 2024.05.17 |
new Date().getTime(); (0) | 2024.05.16 |
실무에서 자주 쓰이는 Function Method (0) | 2024.05.12 |
Java Script -This, 실행컨텍스트, prototype, closure (feat. Elice) (0) | 2024.05.09 |