fetch는 XMLHttpRequest라는 오래된 기술을 대체하기 위해 등장한 브라우저에 기본으로 내장된 API며, Promise 기반으로 동작하여 비동기 통신을 깔끔하게 처리할 수 있다.
먼저 fetch의 가장 기본적인 사용법은 URL을 인자로 넣어 호출하는 방법이다.
fetch('https://api.test.com')
fetch를 사용할 때 주의해야 할 점은 .then()을 두 번 사용해야 원하는 데이터에 접근할 수 있다는 것이다.
첫번째 .then()이 받는 response 값은 JSON 데이터가 아닌 HTTP 응답의 헤더 정보만을 담고 있는 Response 객체이다.
→ 택배를 받았지만 아직 상자를 뜯지 않은 상태라고 볼 수 있다. 내용물은 아직 볼 수 없다.
내용물을 보기 위해서는 json()이나 text() 같은 메서드를 사용해야 한다.
주의: 해당 메서드가 반환하는 데이터는 객체가 아닌 프로미스이다.
.then() 말고 await를 사용하는 방법도 존재한다.
POST는 서버에 데이터를 전송할 때 사용하는 요청이다.
const userData = {
name: 'kim',
age: 25,
}
fetch('https:/api/test/com', {
method: 'POST',
headers: {
'Content-Type': 'application/json'.
}
body: JSON.stringify(userData),
};
method: 요청의 종류를 지정한다 (기본값 = GET)
headers: 요청에 대한 추가 정보를 담는다
body: 서버로 전송할 데이터를 담는다.
이때 JSON.stringify()를 사용해 문자열로 변환해야 하는데, HTTP는 객체를 직접 전송할 수 없기 때문에, JSON 문자열로 직렬화하기 위해 JSON.stringify를 사용한다
GET은 서버에서 데이터를 받아올 때 사용하는 요청이다.
fetch('https:/api/test/com/users');
이때 서버로 보낼 데이터가 없기 때문에 POST와 같이 headers, body를 담을 필요가 없다.
DELETE는 서버에 존재하는 리소스를 삭제할 때 사용하는 요청이다.
fetch('https://api/test/com/users/1', {
method: 'DELETE'
});
이때 삭제 대상은 URL로 식별한다.
PUT은 리소스를 전체 수정할 때 사용하는 요청이다.
fetch('https://api/test/com/users/1', {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'kim',
age: 20
})
})
기존 데이터를 통째로 교체할 때 사용하고, 일부 필드만 보내면 나머지는 사라질 수 있는 위험이 있다.
PATCH는 PUT과 달리 리소스를 부분 수정할 때 사용하는 요청이다.
fetch('https://api/test/com/users/1', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
age: 21
})
})
기존 데이터는 유지한 채로 일부만 변경한다.
HTTP 요청을 보내면 서버는 처리 결과를 숫자로 알려주는데 이를 상태 코드(Status Code)라고 한다.
200~299는 요청이 정상적으로 처리된 경우를 나타낸다.
200 OK : 요청 성공 (데이터 반환 있음)
201 Created : 리소스 생성 성공
204 No Content : 성공했지만 응답 데이터 없음
400~499는 요청에 문제가 있는 경우이다.
400 Bad Request : 요청 형식이 잘못된 경우
401 Unauthorized : 인증 필요
403 Forbidden : 권환 없음
404 Not Found : 리소스 없음
500~599는 서버 내부 문제를 뜻한다.
500 Internal Server Error : 서버 에러
502 / 503 → 서버 장애 또는 과부화
Q. 그럼 method와 headers를 fetch를 사용할 때마다 계속 적어줘야 될까?
A. 그건 아니다. Request 객체를 사용하면 된다
fetch는 URL 문자열 대신 Request 객체를 인자로 받을 수도 있다.
Request 객체를 생성하고 요청에 필요한 URL, 메서드, 헤더, 바디를 하나의 객체로 미리 만들어두고 재사용하거나 모듈화하여 관리할 수 있다.
function createPostRequest(userData) {
return new Request('https://api.example.com/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(userData)
});
}
const req = createPostRequest({ name: 'kim' });
fetch(req)
.then(res => res.json())
.then(data => console.log(data));
fetch는 네트워크 장애같은 요청 자체가 실패한 경우에만 에러를 발생시킨다.
그 말은 404 Not Found 같은 에러가 발생하였을 때는 이를 실패로 보지 않는다.
Q. 그럼 어떻게 에러처리하나요?
A. response.ok 또는 response.status
위와 같은 상황을 해결하기 위해 response의 ok 속성을 직접 확인해야 한다.
async function fetchData() {
try {
const response = await fetch('https://api.example.com/no-such-page');
if (!response.ok) {
throw new Error(`에러 발생: ${response.status}`);
}
// response.ok가 true일 때만 이 코드가 실행
const data = await response.json();
console.log(data);
} catch (error) {
// 네트워크 에러 또는 우리가 throw한 HTTP 에러가 여기서 잡힘
console.error(error.message);
}
}
fetch에는 timeout옵션이 내장되어 있지 않다.
Q. setTimeout을 쓰면 되지 않을까?
A. setTimeout만 사용하면 요청 자체를 중지하진 못한다. 또한 늦게 온 응답이 잘못된 상태를 덮어쓸 수 있다.
그럼 뭐 쓰는데? → setTimeout + AbortController
AbortController는 취소 신호를 만들고 보낼 수 있는 컨트롤러이다.
AbortController를 사용하여 타임아웃을 구현하는 순서는 4단계로 나눌 수 있다.
const controller = new AbortController();
const signal = controller.signal;
const timeout = setTimeout(() => controller.abort(), 5000);
fetch(url, {
signal: controller.signal
})
.then(response => {})
.then(data => {
clearTimeout(timeout
})
.catch(error => {
if(error.name === 'AbortError')
// 에러 처리
}
fetch는 보안상의 이유로 기본적으로 다른 도메인으로 요청을 보낼 때 인증 정보를 포함하지 않는다
인증 정보를 보내고 싶으면? credentials 사용하면 된다.
credentials 옵션
fetch('https://api.test.com/me', {
credentials: 'omit' | 'same-origin' | 'include'
})
fetch는 한 번만 읽을 수 있다.
그 이유는 body는 stream이라서 1번만 소비 가능하다.
stream이란 데이터를 한 번에 다 주는 게 아니라, 흐르듯이 조금씩 전달하는 방식이다
서버 응답이 매우 클 수도 있기 때문에 조금씩 나눠서 전달하는 방식을 택한 것이다.
따라서 이미 흘러간 데이터를 읽을 수 없기 때문에 res.json()을 두 번 사용하면 에러가 발생한다.
const res = await fetch(url);
await res.json();
await res.json(); // 에러
const clone = res.clone();
await res.json();
await clone.text();
만약 두 번 사용하고 싶다면 clone을 사용하여 여러 번 읽을 수 있다.
clone()은 stream을 복사해서 두 번 읽을 수 있게 만들어준다.
fetch 메서드의 기본 개념에 대해서 알아보았다.
fetch 메서드를 공부하게 된 이유는 나만의 커스텀 fetch 라이브러리를 만들어서 배포하고 싶다는 목표가 생겼기 때문이다.
내 목표를 이루기 위해 앞으로 fetch를 더욱 자세히 학습하고, 나아가 axios 라이브러리 또한 깊이 있게 학습할 예정이다.