HTTP 요청 방식은 크게 두 가지가 있다.
- XMLHttpRequest
: 오래된 브라우저에서도 사용 가능
: 에러 처리 코드 포함, 코드가 직관적
: 프로미스 코드는 별도로 추가해줘야 함 - fetch API
: 최신 문법인 fetch API를 이용해서 위의 'sendHttpRequest' 함수를 보다 간략하게 작성할 수 있다.
: fetch API 자체가 프로미스를 리턴하므로 별도 프로미스 선언을 하거나 적용할 필요 없다.
: 브라우저 호환성이 떨어진다. (ex: IE) - Axios
: HTTP 통신을 편하게 할 수 있는 JS 라이브러리인데, 아래에 써놓았으니 일단 기초인 위 두 방식부터 알고 가자.
JS (XMLHttpRequest ver.)
const listElement = document.querySelector(".posts");
const postTemplate = document.getElementById("single-post");
const form = document.querySelector("#new-post form");
const fetchButton = document.querySelector("#available-posts button");
// HTTP 요청 보내기 boilerplate
function sendHttpRequest(method, url, data) {
const promise = new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(method, url);
xhr.responseType = "json";
// xhr.onerror에서는 일부 요청 에러만 처리해주기 때문에, onload에서 추가로 별도 에러 처리 설정.
xhr.onload = function () {
if (xhr.status >= 200 && xhr.status < 300) {
// 서버 요청 정상 응답
resolve(xhr.response);
} else {
// 에러 처리
console.log(xhr.response);
reject(new Error("Something went wrong!"));
}
};
// XMLHttpRequests.onerror() : 네트워크 오류(요청 전송 실패) 또는 요청 시간 초과 시에만 작동. 요청에는 성공했으나 주소가 틀려서 실패하는 경우(응답 반환O)에는 작동 X.
xhr.onerror = function () {
reject(new Error("Failed to send request!"));
};
xhr.send(JSON.stringify(data)); // for "POST". 인자로 받은 data를 JSON으로 변환.
});
return promise;
}
// promise ver
// function fetchPosts() {
// sendHttpRequest("GET", 'https://jsonplaceholder.typicode.com/posts').then(responseData => {
// const listOfPosts = responseData;
// for (const post of listOfPosts) {
// const postEl = document.importNode(postTemplate.content, true);
// postEl.querySelector('h2').textContent = post.title.toUpperCase();
// postEl.querySelector('p').textContent = post.body;
// listElement.append(postEl);
// }
// })
// }
// async await ver
async function fetchPosts() {
const responseData = await sendHttpRequest(
"GET",
"https://jsonplaceholder.typicode.com/posts"
);
const listOfPosts = responseData;
for (const post of listOfPosts) {
const postEl = document.importNode(postTemplate.content, true);
postEl.querySelector("h2").textContent = post.title.toUpperCase();
postEl.querySelector("p").textContent = post.body;
postEl.querySelector("li").id = post.id;
listElement.append(postEl);
}
}
// "POST" 요청
async function createPost(title, content) {
const userId = Math.random();
const post = {
title: title,
body: content,
userId: userId,
};
sendHttpRequest("POST", "https://jsonplaceholder.typicode.com/posts", post);
}
// FETCH 요청
fetchButton.addEventListener("click", fetchPosts);
form.addEventListener("submit", (e) => {
e.preventDefault();
let enteredTitle = form.querySelector("#title").value;
let enteredContent = form.querySelector("#content").value;
createPost(enteredTitle, enteredContent);
});
// DELETE 요청
listElement.addEventListener("click", function (e) {
let postId = e.target.closest("li").id;
if (e.target.textContent == "DELETE") {
sendHttpRequest(
"DELETE",
`https://jsonplaceholder.typicode.com/posts/${postId}`
);
e.target.closest("li").remove();
}
});
JS (fetch API ver.)
function sendHttpRequest(method, url, data) {
return fetch(url, { method: method, body: JSON.stringify(data) }).then(
(response) => {
// XHR 방식의 onload에서 에러 처리하는 부분
if (response.status >= 200 && response.status < 300) {
return response.json(); // json으로 변환 안 하면 iterable한 객체로 인식 못함
} else {
return response.json().then(err => {
console.log(err);
throw new Error('Something went wrong! - Server Side');
})
}
})
// XHR 방식의 onerror에서 에러 처리하는 부분
.catch(error => {
console.log(error);
throw new Error('Something went wrong!');
});
}
// fetchPosts()에 try catch 구문 추가
async function fetchPosts() {
try {
const responseData = await sendHttpRequest(
"GET",
"https://jsonplaceholder.typicode.com/posts"
);
const listOfPosts = responseData;
for (const post of listOfPosts) {
const postEl = document.importNode(postTemplate.content, true);
postEl.querySelector("h2").textContent = post.title.toUpperCase();
postEl.querySelector("p").textContent = post.body;
postEl.querySelector("li").id = post.id;
listElement.append(postEl);
}
} catch {
alert(error.message);
}
}
*then 안에서 또 then 체인이 일어나는 현상은 바람직하지 못하지만, 일단 fetch API에서는 이런 방식으로 에러를 처리한다고 한다.
HTML
<template id="single-post">
<li class="post-item">
<h2></h2>
<p></p>
<button>DELETE</button>
</li>
</template>
<section id="new-post">
<form>
<div class="form-control">
<label for="title">Title</label>
<input type="text" id="title" />
</div>
<div class="form-control">
<label for="content">Content</label>
<textarea rows="3" id="content"></textarea>
</div>
<button type="submit">ADD</button>
</form>
</section>
<section id="available-posts">
<button>FETCH POSTS</button>
<ul class="posts"></ul>
</section>
- 요청 '헤더' 추가 작업
: 일부 API에서는 전송하는 데이터의 유형을 설명해야 하거나, 추가적인 인증 데이터를 필요로 하는 경우가 있다. 이때 헤더가 전송되는 요청에 첨부할 수 있는 메타데이터 역할을 한다.
// fetch API 헤더 추가 예시
return fetch(url, {
method: method,
body: JSON.stringify(data),
headers: { "Content-Type": "application/json" }, // 헤더 추가
}).then((response) => {
return response.json();
});
// XMLHttpRequest 헤더 추가 예시
const xhr = new XMLHttpRequest();
xhr.setRequestHeader("Content-Type", "application/json");
Axios.js
- Axios란?
: Promise API를 활용한 비동기 통신 js 라이브러리. - 왜 사용하는가?
- HTTP와 통신할 때, XMLHttpRequest 방식은 투박하고(onload에서 따로 error를 또 처리해줘야 됨) fetch API는 에러 처리가 복잡하기 때문. => 프로젝트가 커질수록 코드가 더욱 복잡해지기 때문에 간편하게 axios 라이브러리 사용.
- 에러 처리도 알아서 잘 해준다. 기존의 두 방식은 http 요청 url을 잘못 입력한 경우를 에러로 인식하지 못했는데, axios는 에러로 인식해줘서 별도로 조건을 걸어주지 않아도 된다.
위 코드에 Axios를 적용하면 위에서 썼던 'sendHttpRequest' 함수는 더 이상 필요 없어진다.
아래는 fetchPosts()가 sendHttpRequest() 대신 axios로 HTTP 요청을 보내는 예시이다.
async function fetchPosts() {
try {
const response = await axios.get( // sendHttpRequest()를 axios.get(url)으로 대체
'https://jsonplaceholder.typicode.com/posts'
);
console.log(response);
const listOfPosts = response.data;
for (const post of listOfPosts) {
const postEl = document.importNode(postTemplate.content, true);
postEl.querySelector('h2').textContent = post.title.toUpperCase();
postEl.querySelector('p').textContent = post.body;
postEl.querySelector('li').id = post.id;
listElement.append(postEl);
}
} catch (error) {
// 요청 주소 에러를 포함한 모든 에러를 여기서 다 처리해줌
alert(error.message);
console.log(error.response);
}
}
이를 통해 fetch된 post를 콘솔로 확인해보면, json으로 변환하지 않았는데도 알아서 객체 데이터가 js에서 사용할 수 있는 스냅샷으로 변환되어있는 것을 확인할 수 있다.

POST 요청도 axios.post로 대체할 수 있다.
이때, 헤더 구조를 따로 추가하지 않아도 axios에서 알아서 넣어주니 참 좋다.
// "POST" 요청
async function createPost(title, content) {
const userId = Math.random();
const post = {
title: title,
body: content,
userId: userId,
};
axios.post("https://jsonplaceholder.typicode.com/posts", post);
}
DELETE 요청도 마찬가지로 axios 방식으로 바꿔보자.
// DELETE 요청
listElement.addEventListener("click", function (e) {
let postId = e.target.closest("li").id;
if (e.target.textContent == "DELETE") {
axios.delete(`https://jsonplaceholder.typicode.com/posts/${postId}`);
e.target.closest("li").remove();
}
});'JS' 카테고리의 다른 글
| 번들링을 하는 이유? (0) | 2024.09.06 |
|---|---|
| 모듈화 정리 (0) | 2024.09.06 |
| HTTP 통신을 해보자 (0) | 2024.08.30 |
| 프로미스 번외 (0) | 2024.08.29 |
| 콜백 지옥, 프로미스, Async & Await (0) | 2024.08.29 |