이번 포스팅에서는 JavaScript에서 지원하는 내장객체인 fetch , 그리고 외부라이브러리 axios를 이용해서 Ajax 기능을 구현해보고자 한다.
< 목차 >
- promise
- fetch
- axios
1. promise
fetch 와 axios를 사용하기 위해서는 promise(프로미스) 객체에 대한 이해가 선행되어야 한다. 프로미스 객체가 어떤 것인지 간략하게 알아보고 넘어가자. (심도 있는 설명은 나중에 따로 포스팅 할 예정,,)
프로미스 객체를 사용하는 이유에 대해 먼저 간단히 언급하면, 다음과 같이 함수 안에 비동기 코드가 들어갈 경우 함수를 호출해서 비동기의 결과값을 얻는 것이 불가능하기 때문이다. 함수를 사용했을 때 비동기의 결과물을 얻고 싶은 경우 사용하는 것이 프로미스 객체이다. 즉, 비동기적인 처리를 동기적인 방식으로 사용하고 싶기 때문에 쓰는 것이 프로미스 객체라고 생각하면 될 듯하다.
프로미스는 기본적으로 객체(Object)이다. 다음과 같은 구문을 통해 우리는 프로미스 객체를 생성할 수 있다.
// 프로미스는 객체이다.
const pr = new Promise()
프로미스 객체는 인자값으로 콜백함수를 받는다. 그리고 콜백함수는 resolve와 reject라는 매개변수를 가진다. resolve는 성공에 대한 부분이고 reject는 실패에 대한 부분이라고 생각하면 된다.
프로미스에는 상태(states)라는 개념이 있다. 여기서 얘기하는 상태란 프로미스의 처리 과정을 의미하고 new Promise( )로 프로미스를 생성하고 종료될 때까지 총 3가지의 상태를 갖는다.
- Pending(대기) : 비동기 처리 로직이 아직 완료되지 않은 상태
- Fulfilled(이행) : 비동기 처리가 완료되어 프로미스가 결과값을 반환해준 상태
- Rejected(실패) : 비동기 처리가 실패하거나 오류가 발생한 상태
프로미스를 생성할 때 콜백함수의 인자값으로 받은 resolve가 실행되면 프로미스 객체는 이행상태가 되고 then( ) 함수를 이용해 결과값을 받아올 수 있다. 그리고 reject가 실행되면 프로미스 객체는 실패상태가 되고 실패처리에 대한 결과값은 catch( ) 함수를 이용해 받을 수 있다.
다음의 예제코드를 살펴보자.
// 프로미스 객체는 인자값으로 콜백함수를 받는다.
// 콜백함수의 인자값으로는 resolve와 reject가 들어간다.
const pr = new Promise((resolve, reject)=>{
// ...code
// 끝나는 시점을 알았을 때 resolve 함수에다가 내용을 넣는다. (인자값으로)
// 끝나는 시점에서 혹시 에러가 났다면? reject 함수에다가 실패에 대한 내용을 넣는다.
resolve(1)
})
프로미스 객체 pr로부터 1이라는 값을 가져오기 위해서는 다음과 같은 처리를 해주면 된다.
pr.then()
기본적으로 resolve 함수의 인자값으로 들어간 값들은 then( ) 함수를 이용해서 불러올 수 있다. then( ) 메소드 안에서 콜백함수를 작성해 줌으로써 resolve( ) 함수 안에 작성된 1이라는 값을 가져올 수 있게 된다. 해당 과정들은 프로미스가 작동되는 방식이므로 문법적인 부분이라고 생각하고 받아들여야 한다.
// resolve() 함수 안의 1을 then() 메소드 콜백함수의 data라는 매개변수에게 전달
pr.then((data)=>{
console.log(data)
})
// output : 1
resolve( ) 함수 안의 값인 1을 then( ) 메소드 안에 작성된 콜백함수의 data라는 매개변수가 받게 된다. 이제 우리가 맨 처음 작성했던 비동기적인 처리를 하는 함수를 프로미스를 사용하면 다음과 같이 처리할 수 있게된다.
프로미스 객체를 사용하면 이렇게 백그라운드에 들어가는 코드를 컨트롤 할 수 있게 된다. 하지만 이러한 프로미스 객체에도 단점이 존재한다. 다음과 같이 함수 안에서 프로미스 객체가 사용될 경우 결과값이 then( ) 메소드 안에 작성된 콜백함수의 인자값으로 존재하기 때문에 함수 밖에서 결과값을 사용할 수 없게 된다.
function test() {
pr.then(data => {
// data 사용 범위
console.log(data)
})
console.log(data) // output : undefined
}
// test() 함수 밖에서 data에 들어간 결과값 사용 불가
이러한 문제점 속에서 등장한 것이 바로 async , await 문법이다. 함수를 선언할 때 함수 앞에 async를 같이 작성해주면 해당 함수는 프로미스 객체를 관리하는 함수로 선언된다. 그리고 async로 선언된 함수 안에서 프로미스 객체를 사용할 때는 await 구문을 작성해서 사용해주면 된다. 또한 async가 붙은 함수는 return값이 프로미스 객체가 된다.
프로미스 객체 앞에 await을 작성해주면 resolve( ) 함수의 결과물을 변수에 담아서 사용할 수 있게 된다. 위의 코드에서는 data 라는 변수 안에 프로미스 객체 pr의 resolve( ) 값을 담은 것이다. await이라는 말 뜻처럼 pr에 대한 resolve( )의 결과값이 나올 때까지 기다렸다가 data라는 변수에 결과값을 담겠다는 의미이다. (참고로 await 구문은 async가 붙은 함수 안에서만 사용 가능하다.)
async의 역할에 대해 조금 더 알아보자. async는 함수 앞에 붙여서 사용할 수 있는데 기본적으로 프로미스 객체를 return하는 함수를 만든다고 생각하면 된다. async가 붙은 함수 안에서는 return이 곧 resolve( )를 뜻하게 된다. 정리하면, async가 붙은 함수는 return값이 무조건 프로미스 객체로 반환된다. 그리고 return의 결과값은 resolve( )가 된다. 그렇기 때문에 async 함수 안의 코드 영역에서는 프로미스 객체를 마음대로 컨트롤 할 수 있게 된다. await 구문과 함께 사용해서 then( ) 메소드 없이 결과값을 가져와서 쓸 수 있다.
2. fetch
이제 JavaScript 내장객체인 fetch를 사용해서 Ajax를 구현해보자. fetch문은 우리가 앞서 사용한 XMLHttpRequest를 프로미스 객체로 반환한 형태이다. 이전 포스팅을 참고해 보면 XMLHttpRequest( )를 이용해서 Ajax를 구현할 때는 다음과 같이 코드를 작성할 수 있다.
2022.03.06 - [Node.js/express] - Node.js - express (14) Ajax - XMLHttpRequest( )
const userid = document.querySelector('#userid')
const btn = document.querySelector('#idcheck_btn')
const msg = document.querySelector('#msg')
btn.addEventListener('click', clickHandler)
function clickHandler() {
const data = JSON.stringify({userid: userid.value}) //JSON을 스트링으로 변환
// Ajax
const xhr = new XMLHttpRequest()
// Request method , URL 지정
xhr.open('POST', '/idcheck', true) // 첫번째 인자값은 request method, 두번째 인자값은 url
// Request Header(요청헤더) 조작
xhr.setRequestHeader('Content-type', 'application/json')
// Request Body에 데이터를 넣어서 보내기
xhr.send(data)
// xhr.send()에 의해 서버로 내용이 전달되고 응답을 건내받으면 아래의 콜백함수가 실행된다.
xhr.onreadystatechange = () => {
if (xhr.readyState == 4 && xhr.status == 200) {
try {
// 가입가능 1 , 가입불가능 0
const {result} = JSON.parse(xhr.response)
if (result === 0) throw new Error('아이디가 중복되었습니다.')
msg.innerHTML = '사용 가능한 아이디입니다.'
msg.style.color = 'green'
} catch(err) {
// 가입불가능
msg.innerHTML = '중복된 아이디입니다.'
msg.style.color = 'red'
}
}
}
}
clickHander2( ) 함수를 새롭게 정의해서 fetch문을 사용해보도록 하자.
function clickHandler2() {
const userid = document.querySelector('#userid')
const data = JSON.stringify({userid: userid.value})
const opt = {
method: "POST",
headers: {
"Content-type": "application/json"
},
body: data
}
// fetch문은 프로미스 객체를 반환
// 인자값으로 url과 옵션값(객체)을 전달
fetch('http://localhost:3000/idcheck', opt)
.then((res)=>{ // HTTP header 정보
return res.json() // body 영역의 promise객체
})
.then((rst)=>{ // result 변수에 HTTP body 정보가 담긴다.
console.log(rst)
})
}
fetch( ) 함수는 기본적으로 프로미스 객체를 반환한다. 그렇기 때문에 프로미스 체이닝 방식으로 코드를 작성할 수 있다. 인자값으로는 URL과 옵션값을 넣어준다. URL을 작성하는 부분에는 xhr.open( )에 들어갔던 URL과 마찬가지로 요청을 보낼 URL을 작성해주면 된다.
옵션값은 객체 형태로 넣어주며 옵션 객체 안에는 method, headers, body 등과 같은 속성값들이 들어간다. 요청과 관련된 정보들이 옵션값으로 들어간다고 생각하면 된다. fetch( )를 사용한 요청의 경우 기본값이 "GET"방식이기 때문에 만약 "POST" 방식의 요청을 보내고 싶다면 method를 선언해줘야만 한다. xhr.setRequestHeader( )에 들어갔던 내용들은 opt 객체의 headers 속성값으로 전달해준다. headers 속성값으로 Request Header를 조작할 수 있다. 그리고 body 속성값으로는 전달할 데이터를 넣어주면 된다.
fetch( ) 함수가 반환한 프로미스 객체의 결과값을 받기 위해서는 then( ) 메소드를 사용하면 되는데 받게되는 결과값 안에는 body 영역의 내용만 담겨 있는 것이 아니다. HTTP 패킷의 Response Headers 정보들 역시 담겨 있기 때문에 body 영역의 정보만를 뽑아내기 위해서는 return을 사용해서 다시 body영역에 해당하는 정보를 담고 있는 프로미스 객체를 반환해야만 한다. return res.json( ) 을 통해 프로미스 객체로 body 영역을 내보낸다. 이후 다시 then( ) 메소드를 사용하면 우리가 원했던 body 영역의 정보를 가져올 수 있다. (이러한 방식을 프로미스 체이닝이라 한다.)
3. axios
axios는 외부라이브러리를 사용해서 Ajax를 구현하는 방식이다. 우선 다음과 같이 외부라이브러리를 사용할 수 있게끔 연결해주는 작업이 선행되어야 한다.
<!--외부라이브러리 연결-->
<script type="text/javascript" src="https://unpkg.com/axios@0.26.0/dist/axios.min.js"></script>
axios를 사용해서 clickHandler3( ) , clickHandler4( ) 함수를 작성해보면 다음과 같다.
function clickHandler3(){
const data = {userid:userid.value}
// axios는 객체 형태로 데이터를 전달해도 string으로 변환해 준다.
// app.post('url',()=>{})과 비슷한 형태로 작성
// 로딩 만들기
axios.post('http://localhost:3000/idcheck', data, {"Content-type":"application/json"})
.then( (response) => {
console.log(response.data)
// 로딩 지우기
})
}
async function clickHandler4() {
const data = {userid: userid.value}
// 요정을 보내기 전
// 로딩페이지 만들기
try {
const response = await axios.post('http://localhost:3000/idcheck', data, {"Content-type": "application/json"})
} catch (err) {
console.log(err)
}
// 요청이 완료된 후
// 로딩 끝~
console.log(response.data)
}
axios는 우리가 express에서 라우터를 생성할 때 작성했던 코드와 유사한 형태로 작성한다. app.post( 'URL' , ( )=>{ } ) 과 비슷한 형태로 axios.post( 'URL' , { } , { } ) 와 같이 사용한다. 두번째와 세번째 인자값으로는 콜백함수가 아니라 객체가 들어간다. 비동기 통신을 하기 위해 필요한 옵션 내용들이 객체형태로 들어간다고 생각하면 된다. axios는 fetch( ) 함수에서처럼 옵션값이 한 객체 안에 모두 들어간 형태가 아니라 { body } 와 { header }를 구분지어서 인자값으로 전달한다. body 부분에는 data를 객체형태로 넣어주고 header 부분에는 {"Content-type": "application/json"} 을 넣어준다. JSON 포맷으로 데이터를 전달하고 있기 때문에 요청헤더 부분을 조작해주는 것이다.
axios의 경우 외부라이브러리를 사용하는 형태이기 때문에 편의성이 많이 개선되었다. 실제로 데이터를 보낼 때는 string 형태로 보내줘야 하지만 axios의 경우 객체 안의 내용을 알아서 string으로 변환한 후에 내용을 보내준다. 따라서 axios의 인자값으로 데이터를 넣어줄 때는 스트링값이 아닌 객체 자체를 넣어주면 된다.
axios는 fetch( ) 함수와 마찬가지로 프로미스 객체를 반환하고 있기 때문에 then( ) 메소드를 사용해서 결과값을 받아올 수 있다. 또한 프로미스 객체를 반환하고 있기 때문에 async , await 역시 사용 가능하다. async , await 을 사용해서 만든 clickHandler4( ) 함수를 살펴보면 axios.post( )를 이용해서 받아온 결과값을 response라는 변수에 담아서 사용하고 있는 것을 확인할 수 있다.
'Node > Express' 카테고리의 다른 글
Node.js - express (17) multer (1) (0) | 2022.03.15 |
---|---|
Node.js - express (16) CORS (0) | 2022.03.13 |
Node.js - express (14) Ajax - XMLHttpRequest( ) (0) | 2022.03.06 |
Node.js - express (13) JWT 로그인 인증 (0) | 2022.03.05 |
Node.js - express (12) Buffer , Hash , JWT (0) | 2022.03.03 |