Javascript에서 비동기 통신하는 방법
Javascript에서 비동기 http 통신을 하는 방법은 대표적으로 세 가지가 있다.
- AJAX
- Javascript의 AJAX
- jQuery의 AJAX
- axios
- fetch
이 중에서 많이 사용되는 AJAX와 axios에 대해알아보자.
1. AJAX
1.1 AJAX란?
AJAX = Asynchronous Javascript And XML
비동기 자바스크립트와 XML란 의미로, 데이터를 이동하고 화면을 구성하는데 있어 갱신 없이 필요한 데이터를 서버로 보내고 응답을 가져오는 방법이다.
AJAX 자체는 특정 기술을 말하는 게 아니다.
2005년 Jesse James Garrett이 처음 만들어낸 말로,
HTML 혹은 XHTML, CSS, JavaScript, DOM, XML, XSLT, 그리고 가장 중요한 XMLHttpRequest 객체를 비롯하여
기존의 여러 기술을 사용하는 새로운 접근법을 설명하는 "용어"이다.
대표적으로
(1) XMLHttpRequest 객체를 사용하는 "Javascript AJAX"
(2) "jQuery AJAX"
가 있다.
1.2 AJAX를 사용하는 이유?
위에서 언급한 다양한 기술들을 AJAX 모델로 결합하면 비동기적 웹 서비스를 만들 수 있다.
AJAX은 다음 두 가지 작업을 할 수 있도록 해준다.
- 페이지 새로고침이나 페이지 이동 없이 서버에 요청을 보냄
- 서버로부터 데이터를 받고(=응답을 받음) 작업을 수행함
즉, 데이터를 이동하고 화면을 구성하는데 있어 화면 갱신없이 필요한 데이터를 서버로 보내고, 또 응답을 가져올 수 있다는 것이다.
웹 사이트 중에서 페이지 전환 없이 새로운 데이터를 불러오는 사이트들이 대부분 AJAX 기술을 사용하고 있다고 보면 된다.
1.3 XMLHttpRequest(XHR)
Javascript AJAX 구현에 있어, XMLHttpRequest 객체는 반드시 필요하다.
하지만 IE7 이하 버전에서는 지원하지 않으므로 이때는 XHR이 아닌 ActiveXObject를 사용해야 한다.
1.4 AJAX vs Socket.io
1.4.1 socket.io
소켓은 '연결➡통신➡종료' 단계로 이뤄진다. 소켓 통신하는 방식으로 Non-Persistent와 Persistent 두 가지가 있다.
Non-Persistent
소켓 연결을 하고, 통신을 하고, 종료를 한다. 다음 통신을 하면 다시 연결을 하고, 통신이 끝나면 또 종료를 한다.
이렇게 연결하면 페이지를 계속해서 움직이게 할 수 있다. 페이지를 이동하거나 새로고침하면 js파일을 다시 불러서 다시 소켓 연결을 한다.
하지만 이는 지연시간이 많이 소모된다. 소켓이 연결되면 그 이후로 주고받는 통신은 빠르긴 하지만, 연결되기 위해 소모되는 시간이 너무 크다.
Persistent
Persistent는 이 반대이다. 한 번 소켓 연결을 하면 종료하지 않고 계속해서 통신을 한다. 그래서 연결하는 지연 시간이 단 한 번만 존재한다.
하지만 페이지를 이동하거나 새로고침을 하면 어쩔 수 없이 연결이 끊긴다. frame을 이용하거나 angular.js 등의 방식으로 Single page web을 구현하면 이 문제를 해결할 수 있다.
1.4.2 AJAX
보통 jQuery AJAX를 사용한다. 페이지 내부에서 자바스크립트로 데이터를 GET과 POST 방식으로 요청하면 콜백함수를 실행하여 응답한다.
이때 콜백함수에 따라 페이지를 갱신할 수 있게 되는 것이다. 따라서 연결과 종료를 위한 지연시간이 따로 없고, 페이지를 보내고 받는 시간만 존재한다.
또한 단일 페이지 내부에서 얼마든지 실행할 수 있다는 장점이 있다.
하지만 보내는 방식이 HTTP 방식이기 때문에 HTTP 프로토콜을 따라야 한다는 단점이 있다. 즉, 보내는 패킷이 http 방식으로 헤더 등의 필수 정보를 반드시 포함해야 한다는 것이다.
물론 자동으로 정의가 되어서 보내지지만, 이러한 작은 데이터들이 모여서 큰 트래픽을 소모할 수도 있다.
1.4.3 AJAX vs Socket.io
위 그래프에 따르면 AJAX와 Socket.io의 전송 속도는 거의 비슷하다.
그러나 같은 데이터를 전송해도 Socket.io 방식이 AJAX 방식보다 더 적은 트래픽을 소모한다. 이는 후에 호스팅을 할 때 트래픽에 따라 서버 비용이 매우 늘어날 수 있다는 점을 고려하면 엄청 중요한 고려사항이다.
그렇다고 Socket.io가 무조건 더 좋다는 건 아니다. Websocket을 적용할 수 없는 브라우저도 있고, 소켓의 정보를 서버의 메모리상에 전부 저장하고 있어야 하기 때문에 메모리 부담이 너무 커진다. 그래도 트래픽 부담보다는 메모리 부담을 받아들이는 게 더 낫다.
또한 페이지 이동이 잦을수록 Socket.io는 Non-Persistent에 가까워진다. 따라서 성능이 AJAX에 비해 점점 떨어지게 된다.
따라서 자신의 웹 서버 기능과 목적을 잘 고려하여 두 방식 중 하나를 잘 선택해야 한다.
페이지 이동이 잦을 경우: AJAX
트래픽 소모를 최소화할 경우: Socket.io
1.5 Javascript의 AJAX와 jQuery의 AJAX
- Javascript의 AJAX
보통 상황에서 Javascript AJAX가 아닌 jQuery AJAX를 생각하므로 자세히 다루진 않겠다.
사용 방법
- jQuery의 AJAX
보통 상황에서는 Javascript의 AJAX가 아니라, jQuery AJAX를 생각한다.
Javascript AJAX로 구현하는 것보다 더 명시적이게 작성할 수 있다.
1. 데이터 형식
이름에는 XML이 들어있지만 JSON, XML, HTML, 일반 텍스트 등 다양한 포맷을 주고 받을 수 있다. 그 중 AJAX에서는 크게 CSV, JSON, XML 포맷으로 보내게 된다.
CSV 포맷
콤마(,)로 데이터 속성을 나누고 줄바꿈으로 데이터를 나눈다.
- 장점: 용량이 적다.
- 단점: 가독성이 떨어진다.
XML 포맷
CSV 포맷의 단점인 가독성 부분을 개선하기 위해 나온 형식이다. 속성과 데이터를 구분한다.
- 장점: CSV에 비해 가독성이 좋다.
- 단점: 용량이 크고, 데이터가 많아지면 분석력이 떨어진다.
JSON 포맷
Javascript 객체 형태로 데이터를 전송한다.
- 장점: 가독성이 좋고 용량이 적다. Javascript의 일부이다.
- 단점: 여전히 데이터가 많아지면 분석력이 떨어진다.
➡ 요즘은 JSON 포맷을 많이 사용한다.
2. 데이터 전송 방식
여러 방식이 있지만, CRUD 기준으로 살펴보자.
C : Create(생성) = POST 메서드
서버에 정보를 올려달라는 요청이다.
POST를 통해 해당 URI를 요청하면 리소스를 생성한다.
R: Read(읽기) = GET 메서드
서버에서 정보를 불러오는 요청이다.
GET을 통해 해당 리소스를 조회하고 해당 도큐먼트에 대한 자세한 정보를 가져온다.
U: Update(갱신) = PUT, PATCH 메서드
정보를 바꾸는 요청이다.
PUT은 데이터 전체를 바꾸고 싶을 때, PATCH는 데이터의 일부만 수정하고 싶을 때 사용한다.
D: Delete(삭제) = DELETE 메서드
정보를 지우는 요청이다.
DELETE를 통해 리소스를 삭제할 수 있다.
GET과 POST
➡ 브라우저 특성상 지원하지 않는 문제가 있어서 POST, GET을 많이 사용한다.
단순히 데이터를 읽어들이는 경우는 GET,
생성, 변경, 삭제를 하는 경우는 POST를 사용한다.
GET 방식은 URL에 정보가 그대로 담기기 때문에 민감한 정보는 GET 방식을 사용하지 않는다.
3. 사용법
Javascript AJAX로 구현하는 것보다 더 명시적으로 작성할 수 있다.
jQuery의 AJAX 사용 방식은 1.5 이전과 이후로 나뉜다.
1.5 이전
1.5 이전에는 success, fail, complete 옵션을 이용한 callback 방식을 사용했다.
$.ajax({
url : '/member/1',
type : 'get',
data : data,
success : function(data) {
// 성공
},
error : function(error) {
// 에러
},
complete : function() {
// 항상
}
});
1.5 이후
1.5 이후에는 promise 기반으로 변경되었다.
- done(성공), fail(에러), always(항상) - 1.6에 등장
- then(성공, 에러) - 1.8에 등장
$.ajax({
url: "/api/test",
type: 'get'
})
.done(() => {
// 요청 성공시
})
.fail(() => {
// 요청 실패시
})
.always(() => {
// 항상
});
API 접근하기
AJAX를 이용하여 해당 API에 직접 접근하여 데이터를 가져오는 경우에는 보안과 관련한 이슈 문제가 있다.
따라서 해당 API에 접근하기 위해서는 호출하는 Header에 발급 받은 API Key를 삽입하여 호출해야 한다.
jQuery AJAX의 경우, beforeSend 부분을 추가하여 쉽게 처리할 수 있다.
beforeSend: (xhr) => {
xhr.setRequestHeader("ApiKey", "API키 입력");
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
}
beforeSend는 AJAX를 요청하기 직전의 콜백함수로서, jqXHR 객체를 수정할 수 있다.
위의 예제 경우에도 jqXHR에 헤더값을 추가한 것이다.
2. axios
Node.js와 브라우저를 위한 http 통신 라이브러리.
fetch 방법과 달리 크로스 브라우징에 최적화되어 있어 IE11까지 모조리 지원하므로 호환성이 좋다.
promise 기반이므로 심플하게 사용할 수 있고, 기능이 많아서 몇몇 프론트 프레임워크에서 사용을 권장하고 있다.
then(성공), catch(실패), then(항상) 으로 실행할 수 있다.
특이하게 항상 실행되게 하려면 then ➡ catch ➡ then 과 같이 마지막 then을 선언해주면 된다.
axios.get('http://localhost:3000')
.then((res) => {
// 성공
})
.catch((err) => {
// 실패
})
.then(() => {
// 항상
});
장점
- Javascript의 라이브러리이다.
$ npm i axios
- 구현 브라우저를 지원한다.
- 응답 시간 초과를 설정할 수 있다.
- 요청을 중단(request aborting)할 수 있다.
- JSON 데이터 자동 변환이 가능하다.
- Node.js에서 사용이 가능하다.
- catch에 걸렸을 때 .then을 실행하지 않고 해당 에러 로그를 콘솔에 출력해준다.
- return값이 promise 객체 형태이다.
단점
- 라이브러리를 설치해야 한다.
axios()
구성(configuration) 설정을 axios()에 전달하여 요청할 수 있다.
axios(config)
➡ 예제1. 여러 구성 설정을 작성하여 GET 요청하기
axios({
method: 'get',
url: '/user/12345',
data: {
firstName: 'heo',
lastName: 'daeun',
}
});
➡ 예제2. GET 요청 실행하기
axios.get('/user?ID=12345')
.then((res) => {
// GET 요청에 성공했을 때
console.log(res);
})
.catch((err) => {
// 에러가 났을 때
console.error(err);
})
.then(() => {
// 항상 실행되는 함수
});
위와 동일하지만 옵션을 주고자 할 때는 다음과 같이 요청한다.
axios.get('/user', {
params: {
ID: 12345
}
})
...
async / await를 사용하고 싶다면 async 함수/ await 메소드를 작성한다.
async function getUser() {
try {
const res = await axios.get('/user?ID=12345');
console.log(res);
} catch (err) {
console.error(err);
}
}
➡ 예제3. POST 요청 실행하기
axios.post('/user', {
firstName: 'Fred',
lastName: 'Flintstone'
})
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err);
});
별칭 메소드
혹은 별칭 메소드를 사용하여 편리하게 작성할 수도 있다.
별칭 메소드를 사용하면 설정(config)에서 url, method, data 속성을 지정할 필요가 없다.
axios.put('/user/12345', {
firstName: 'heo',
lastName: 'daeun',
})
별칭 메소드의 종류와 작성법
axios.get(url[, config]) // GET
axios.post(url[, data[, config]]) // POST
axios.put(url[, data[, config]]) // PUT
axios.patch(url[, data[, config]]) // PATCH
axios.delete(url[, config]) // DELETE
axios.request(config)
axios.head(url[, config])
axios.options(url[, config])
동시성(Concurrency)
동시에 여러 요청을 처리하려면 아래와 같이 작성하면 된다.
axios.all(요청1, 요청2)
예제
function getUserAccount() {
return axios.get('/user/12345');
}
function getUserPermissions() {
return axios.get('/user/12345/permissions');
}
axios.all([getUserAccount(), getUserPermissions()])
.then(axois.spread((acct, perms) => {
// 두 요청이 성공했을 경우
}));
인스턴스 생성
.create() 메소드를 사용하여 사용자 정의 구성을 사용하는 axios 인스턴스를 생성할 수 있다.
axios.create([config])
➡ 예제
const instance = axios.create({
baseURL: 'https://some-domain.com/api/',
headers: { 'X-Custom-Header': 'foobar' },
timeout: 1000,
});
인스턴스 메소드
사용 가능한 인스턴스 메소드는 다음과 같다. 설정 구성(=config)이 인스턴스 구성과 병합된다.
axios.get(url[, config])
axios.post(url[, data[, config]])
axios.put(url[, data[, config]])
axios.patch(url[, data[, config]])
axios.delete(url[, config])
axios.request(config)
axios.head(url[, config])
axios.options(url[, config])
axios.getUri([config])
구성 설정(=config)
url 속성만 필수이고, 나머지는 옵션이다. 메소드를 작성하지 않았을 때 default 값은 GET이다.
{
// `url`은 요청에 사용될 서버 URL입니다.
url: '/user',
// `method`는 요청을 할 때 사용될 메소드 이름입니다.
method: 'get', // 기본
// `url` 속성 값이 절대 URL이 아니라면, `url` 앞에 `baseURL`이 붙습니다.
// axios 인스턴스가 상대 URL을 해당 인스턴스의 메소드에 전달하도록
// `baseURL`을 설정하는 것이 편리 할 수 있습니다.
baseURL: 'https://some-domain.com/api/',
// `transformRequest`는 서버에 보내기 전에 요청 데이터를 변경할 수 있습니다.
// 요청 메소드 'PUT', 'POST' 및 'PATCH' 에만 적용 가능합니다.
// 배열의 마지막 함수는 버퍼(buffer)의 문자열이나 인스턴스를 반환해야 합니다.
// ArrayBuffer, FormData 또는 Stream 헤더 객체를 수정할 수 있습니다.
transformRequest: [function (data, headers) {
// 데이터 변환 수행 후, 반환
// ...
return data;
}],
// `transformResponse`는 응답할 데이터에 대한 변경을 전달해
// then/catch에 전달하도록 허용합니다.
transformResponse: [function (data) {
// 데이터 변환 수행 후, 반환
// ...
return data;
}],
// `headers`는 서버에 전송 될 사용자 정의 헤더 입니다.
headers: { 'X-Requested-With': 'XMLHttpRequest' },
// `params`는 요청과 함께 전송 될 URL 매개 변수입니다.
// 일반 객체 이거나 URLSearchParams 객체여야 합니다.
params: {
ID: 12345
},
// `paramsSerializer`는`params`를 직렬화(serializing) 하는 옵션 함수입니다.
// (예: https://www.npmjs.com/package/qs, http://api.jquery.com/jquery.param/)
paramsSerializer: function (params) {
return Qs.stringify(params, {arrayFormat: 'brackets'})
},
// `data`는 요청 본문(request body)으로 전송할 데이터입니다.
// 'PUT', 'POST' 및 'PATCH' 요청 메소드에만 적용 가능합니다.
// 'transformRequest`가 설정되지 않은 경우 다음 유형 중 하나여야 합니다.
// - [ string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams ]
// - 브라우저 전용: FormData, File, Blob
// - Node.js 전용: Stream, Buffer
data: {
firstName: 'Fred'
},
// `timeout`은 요청이 타임 아웃되는 밀리 초(ms)를 설정합니다.
// 요청이`timeout` 설정 시간보다 지연될 경우, 요청은 중단됩니다.
timeout: 1000, // 기본 값: `0` (타임아웃 없음)
// `withCredentials`는 자격 증명(credentials)을 사용하여
// 크로스 사이트 접근 제어(cross-site Access-Control) 요청이 필요한 경우 설정합니다.
withCredentials: false, // 기본 값
// `adapter`는 테스트를 보다 쉽게 해주는 커스텀 핸들링 요청을 허용합니다.
// 유효한 응답(Promise)을 반환해야 합니다. (lib/adapters/README.md 참고).
adapter: function (config) {
// ...
},
// `auth`는 HTTP 기본 인증(auth)이 사용되며, 자격 증명(credentials)을 제공함을 나타냅니다.
// 기존의 `Authorization` 커스텀 헤더를 덮어쓰는 `Authorization` 헤더(header)를 설정합니다.
auth: {
username: 'janedoe',
password: 's00pers3cret'
},
// `responseType`은 서버에서 응답할 데이터 타입을 설정합니다.
// [ 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream' ]
responseType: 'json', // 기본 값
// `responseEncoding`은 응답 디코딩에 사용할 인코딩을 나타냅니다.
// [주의!] 클라이언트 사이드 요청 또는 `responseType`이 'stream'인 경우는 무시합니다.
responseEncoding: 'utf8', // 기본 값
// `xsrfCookieName`은 xsrf 토큰(token)에 대한 값으로 사용할 쿠키 이름입니다.
xsrfCookieName: 'XSRF-TOKEN', // 기본 값
// `xsrfHeaderName`은 xsrf 토큰 값을 운반하는 HTTP 헤더 이름입니다.
xsrfHeaderName: 'X-XSRF-TOKEN', // 기본 값
// `onUploadProgress`는 업로드 프로그래스 이벤트를 처리합니다.
onUploadProgress: function (progressEvent) {
// 네이티브 프로그래스 이벤트(Native Progress Event) 처리 코드
// ...
},
// `onDownloadProgress`는 다운로드 프로그래스 이벤트를 처리합니다.
onDownloadProgress: function (progressEvent) {
// 네이티브 프로그래스 이벤트(Native Progress Event) 처리 코드
// ...
},
// `maxContentLength`는 HTTP 응답 콘텐츠의 최대 크기를 바이트(Bytes) 단위로 설정합니다.
maxContentLength: 2000,
// `validateStatus`는 주어진 HTTP 응답 상태 코드에 대한 약속을 해결할지 거절 할지를 정의합니다.
// `validateStatus`가`true`를 반환하면 (또는`null`,`undefined`) promise를 resolve 합니다.
// 그렇지 않으면 promise가 reject 됩니다.
validateStatus: function (status) {
return status >= 200 && status < 300; // 기본 값
},
// `maxRedirects`는 Node.js에서 리디렉션 가능한 최대 갯수를 정의합니다.
// 0으로 설정하면 리디렉션이 수행되지 않습니다.
maxRedirects: 5, // 기본 값
// `socketPath`는 Node.js에서 사용될 UNIX 소켓을 정의합니다.
// 예: '/var/run/docker.sock'을 사용하여 docker 데몬에 요청을 보냅니다.
// `socketPath` 또는`proxy`만이 지정 될 수 있습니다.
// 둘 다 지정되면`socketPath`가 사용됩니다.
socketPath: null, // 기본 값
// `httpAgent`와`httpsAgent`는 각각 Node.js에서 http와 https 요청을 수행 할 때
// 사용할 커스텀 에이전트를 정의합니다. 이것은 기본적으로 활성화되지 않은 `keepAlive`와 같은
// 옵션을 추가 할 수 있게 합니다.
httpAgent: new http.Agent({ keepAlive: true }),
httpsAgent: new https.Agent({ keepAlive: true }),
// 'proxy'는 프록시 서버의 호스트 이름과 포트를 정의합니다.
// 기존의 `http_proxy` 및 `https_proxy` 환경 변수를 사용하여 프록시를 정의 할 수도 있습니다.
// 프록시 설정에 환경 변수를 사용하고 있다면 `no_proxy` 환경 변수를 쉼표로 구분 된 도메인 목록으로
// 정의하여 프록시 할 필요가 없습니다.
// 환경 변수를 무시하고 프록시를 사용하지 않으려면 `false`를 설정합니다.
// `auth`는 HTTP 기본 인증(Basic Auth)를 사용하여 프록시에 연결하고 자격 증명을 제공해야 함을 나타냅니다.
// 기존의 `Proxy-Authorization` 커스텀 헤더를 덮어쓰는 `Proxy-Authorization` 헤더(header)를 설정합니다.
proxy: {
host: '127.0.0.1',
port: 9000,
auth: {
username: 'mikeymike',
password: 'rapunz3l'
}
},
// `cancelToken`은 요청을 취소하는 데 사용할 수 있는 취소 토큰을 지정합니다.
// (자세한 내용은 해제(Cancellation) 섹션 참조).
cancelToken: new CancelToken(function (cancel) {
// ...
})
}
3. fetch
- Javascript 내장 라이브러리이므로 바로 사용할 수 있다.
- 라이브러리의 업데이트에 따른 에러 방지가 가능하다.
- React Native는 업데이트가 잦아서 라이브러리가 쫓아오지 못하는 경우가 많지만, fetch를 사용하면 이를 방지할 수 있다.
- response timeout API를 제공하지 않아서 네트워크가 발생했을 때 기다려야 한다.
- 지원하지 않는 브라우저가 있어 호환성이 좋지 않다.
- return 값은 promise 객체 형태다.
구현
fetch('request url', {
method: 'POST',
body,
})
.then((res) => {
console.log(res);
});
참조한 자료들
'Back-End > Node.js' 카테고리의 다른 글
[Node.js 교과서] 4. http 모듈로 서버 만들기 (0) | 2021.04.12 |
---|---|
[Node.js 교과서] 3. 노드 기능 알아보기 (0) | 2021.04.12 |
[Node.js 교과서] 2. 알아두어야 할 자바스크립트 (0) | 2021.04.08 |
[Node.js 교과서] 1. 노드 시작하기 (0) | 2021.04.07 |
Node.js에서 JSON 파일에 데이터를 저장하고 읽는 방법 (0) | 2020.10.02 |