[개발자의품격][부트캠프][1기][10차시] JavaScript 주요 포인트 #12 | 고급 문법 - XMLHttpRequest, Fetch API, Promise, Async/Await
JavaScript 주요 포인트 #12
자바스크립트는 AJAX(Asynchronous JavaScript and XML) 이전과 이후로 나뉜다. AJAX로 인해 비동기 통신 및 웹페이지의 특정 일부분만 랜더링 하는 것이 가능해졌다. 이로 인해 웹이 급속도록 발전하게 되었다. AJAX는 동적인 웹 페이지를 만들기 위해 사용한다.
여기에서 다루게 될 내용은 XMLHttpRequest, Fetch API, Promise, Async/Await로서 자바스크립트 전체 문법을 통틀어 매우 중요한 부분이다. 이번 실습은 테스트 서버가 필요하다. 따라서 JSON Server를 실행한다.
JSON Server 실행
json-server --watch db.json
XMLHttpRequest
XMLHttpRequest(XHR) 객체는 서버와 상호작용하기 위해 사용한다. 지금은 XMLHttpRequest 객체를 직접적으로 사용할 일이 잘 없다. jQuery, fetch, axios을 사용하기 때문이다. 이 중에서도 axios를 주로 사용한다. 내부적으로 XMLHttpRequest가 돌아간다.
HTTP method(HTTP 요청 방식)
- GET - 리소스 요청
- POST - 리소스 생성
- PUT - 리소스 수정
- PATCH - 리소스 일부 수정(거의 안 씀)
- DELETE - 리소스 삭제
JSON Server Resources
- http://localhost:3000/posts
- http://localhost:3000/comments
- http://localhost:3000/profile
아래 코드 블록은 XMLHttpRequest 객체를 이용하여 서버의 데이터를 조회, 생성, 수정, 삭제하는 코드이다. 여기에서는 HTTP method(HTTP 요청 방식)와 JSON Server Resources를 이용한다.
<body>
<button onclick="getData()">조회</button>
<button onclick="postData()">생성</button>
<button onclick="putData()">수정</button>
<button onclick="deleteData()">삭제</button>
<script>
// 조회
function getData() {
const xhr = new XMLHttpRequest();
xhr.open("GET", "http://localhost:3000/posts"); // 요청방식과 서버URL을 넣어준다. 준비 상태이다.
xhr.setRequestHeader("content-type", "application/json"); // 요청에 맞는 헤더 값을 설정해야 한다. 서버와는 일반적으로 json 통신하기 때문에 거의 이렇게 쓴다고 보면된다.
xhr.send(); // 서버에 요청을 보낸다.
// 서버로 요청하면 그 결과가 응답이 왔을때 onload에 있는 코드가 실행된다.
xhr.onload = () => {
console.log(xhr);
// 상태 코드 200, 성공
if (xhr.status === 200) {
const res = JSON.parse(xhr.response); // object로 받아 온다.
console.log(res);
return res;
} else {
console.log(xhr.status, xhr.statusText);
}
};
}
// 생성
function postData() {
const xhr = new XMLHttpRequest();
xhr.open("POST", "http://localhost:3000/posts");
xhr.setRequestHeader("content-type", "application/json;charset=UTF-8"); // charset=UTF-8 한글이 깨지는 것 방지한다.
const data = { title: "xmlhttprequest", author: "John Doe" }; // json은 유일한 키값이 항상 존재해하기 때문에 id가 반드시 있어야 한다. id번호는 자동으로 생성된다.
xhr.send(JSON.stringify(data)); // object를 문자열로 변환한다.
xhr.onload = () => {
if (xhr.status === 201) {
// 상태 코드 201, 생성만 201, 조회, 수정, 삭제는 200번이다.
const res = JSON.parse(xhr.response);
console.log(res);
} else {
console.log(xhr.status, xhr.statusText);
}
};
}
// 수정
function putData() {
const xhr = new XMLHttpRequest();
xhr.open("PUT", "http://localhost:3000/posts/2"); // id 2번 수정
xhr.setRequestHeader("content-type", "application/json;charset=UTF-8");
const data = { title: "javascript", author: "jeremy" };
xhr.send(JSON.stringify(data));
xhr.onload = () => {
if (xhr.status === 200) {
const res = JSON.parse(xhr.response);
console.log(res);
} else {
console.log(xhr.status, xhr.statusText);
}
};
}
// 삭제
function deleteData() {
const xhr = new XMLHttpRequest();
xhr.open("DELETE", "http://localhost:3000/posts/2"); // id 2번 삭제
xhr.setRequestHeader("content-type", "application/json;charset=UTF-8");
xhr.send();
xhr.onload = () => {
if (xhr.status === 200) {
const res = JSON.parse(xhr.response);
console.log(res);
} else {
console.log(xhr.status, xhr.statusText);
}
};
}
</script>
</body>
Fetch API
Fetch API를 이용하면 XMLHttpRequest와 동일한 기능을 더 쉽게 구현할 수 있다. Fetch API는 그 자체가 Promise이다.
<body>
<button onclick="getData()">조회</button>
<button onclick="postData()">생성</button>
<button onclick="putData()">수정</button>
<button onclick="deleteData()">삭제</button>
<script>
// 조회
function getData() {
fetch("http://localhost:3000/posts")
// .then(function (response) {
// return response.json(); // json 으로 변환
// })
// .then(function (json) {
// console.log(json);
// });
// 위 조회 코드를 화살표 함수로 구현
.then((response) => response.json()) // json 으로 변환
.then((json) => console.log(json));
}
// 생성
function postData() {
fetch("http://localhost:3000/posts", {
method: "POST",
body: JSON.stringify({
title: "xmllhttprequest",
author: "John Doe",
}),
headers: {
"content-type": "application/json;charset=UTF-8",
},
})
.then((response) => response.json())
.then((json) => console.log(json));
}
// 수정
function putData() {
// id 3번 수정
fetch("http://localhost:3000/posts/3", {
method: "PUT",
body: JSON.stringify({
title: "xmlhttprequest",
author: "John Doe",
}),
headers: {
"content-type": "application/json;charset=UTF-8",
},
})
.then((response) => response.json())
.then((json) => console.log(json));
}
// 삭제
function deleteData() {
// id 3번 삭제
fetch("http://localhost:3000/posts/3", {
method: "DELETE",
})
.then((response) => response.json())
.then((json) => console.log(json));
}
</script>
</body>
Promise
Promise는 비동기 처리에 사용되는 객체이다. 비동기란 언제 끝날지 모르는 작업을 기다리지 않고 다음 작업을 처리하는 것을 말한다.
Promise 미적용
<script>
function getData() {
const xhr = new XMLHttpRequest();
xhr.open("GET", "http://localhost:3000/posts"); // 요청방식과 서버URL을 넣어준다. 준비 상태이다.
xhr.setRequestHeader("content-type", "application/json"); // 요청에 맞는 헤더 값을 설정해야 한다. 서버와는 일반적으로 json 통신하기 때문에 거의 이렇게 쓴다고 보면된다.
xhr.send(); // 서버에 요청을 보낸다.
// 서버로 요청하면 그 결과가 응답이 왔을때 onload에 있는 코드가 실행된다.
xhr.onload = () => {
console.log(xhr);
// 상태 코드 200, 성공
if (xhr.status === 200) {
const res = JSON.parse(xhr.response); // object로 받아 온다.
return res;
} else {
console.log(xhr.status, xhr.statusText);
}
};
}
const data = getData();
console.log(data); // undefined(시차 발생, 서버가 요청에 대한 응답을 보내주기 전에 실행되었다.)
console.log("다음 코드 실행");
</script>
Promise 적용
<script>
// Promise 문법은 다음과 같다.
// const promise = new Promise((resolve, reject) => {
// if ("처리성공") {
// resolve("결과 데이터");
// } else {
// reject("에러");
// }
// });
function getData2(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.setRequestHeader("content-type", "application/json");
xhr.send();
xhr.onload = () => {
if (xhr.status === 200) {
const res = JSON.parse(xhr.response);
resolve(res);
} else {
console.log(xhr.status, xhr.statusText);
reject(xhr.status);
}
};
});
}
// Promise를 적용하면 호출하는 쪽에서 then을 정의하여 사용할 수 있다.
// 서버로부터 응답받기 전에 실행되어 버리면 문제가 되는 코드를 then 함수 내에 작성한다.
getData2("http://localhost:3000/posts").then((res) => console.log(res)); // 2순위 실행
getData2("http://localhost:3000/comments").then((res) => console.log(res)); // 3순위 실행
console.log("다음 코드 실행"); // 1순위 실행
// getData1().then(getData2().then(getData3())) // 참고로 Promise는 이렇게 연결하여 사용할 수 있지만 복잡해진다.
// 아래는 위 코드와 같다.
// const data1 = getData1();
// const data2 = getData2(data1);
// const data3 = getData3(data2);
</script>
Promise 없는 then은 error
<script>
function getCommentData(url) {
const xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.setRequestHeader("content-type", "application/json");
xhr.send();
xhr.onload = () => {
if (xhr.status === 200) {
const res = JSON.parse(xhr.response);
return res;
} else {
console.log(xhr.status, xhr.statusText);
}
};
}
// promise를 적용하지 않았는데 호출하는 쪽에서 then을 사용하면 에러가 발생한다.
getCommentData("http://localhost:3000/posts").then((res) =>
console.log(res)
); // Uncaught TypeError: Cannot read properties of undefined (reading 'then')
</script>
Async/Await
Async/Await는 Promise와 동일한 목적으로 사용한다. 비동기 통신이 완벽히 끝날 때까지 기다린다. 서버와 통신 중 순차적으로 코드를 실행할 때 주로 사용한다. 데이터가 너무 많아서 아래 코드가 마냥 기다리게 하는 상황이 발생할 수 있다. 따라서 서버로 부터 응답 받은 데이터를 다음 코드에서 반드시 필요로 할때 사용한다. 그런 상황이 아니라면 데이터를 덜 받았더라도 나머지 코드가 실행되도록 해야 한다. 상황에 따라 잘 사용하자!
<body>
<button onclick="getData()">조회</button>
<script>
// function 앞에 async를 붙인다.
async function getData() {
const res1 = await fetch("http://localhost:3000/posts"); // 서버로 부터 값이 오기를 기다렸다가 res1에 넣겠다.
// const res1Json = res1.json(); // 콘솔창에 "Promise {<pending>}" 표시되었다. res1Json에 배열 요소가 들어오지 않은 상태이다.
const res1Json = await res1.json(); // await으로 인해 res1Json에 배열 요소가 모두 들어온다.
console.log(res1Json);
}
</script>
</body>