Fetch API와 AJAX
우리는 8장에서 최신 ECMAScript 문법을 통해 더 견고하고 간결한 자바스크립트 코드를 작성하는 방법을 배웠습니다.
이제 다음 단계는 언어 자체를 넘어, 브라우저가 제공하는 웹 API(Web APIs)를 실전적으로 쓰는 것입니다. DOM, 이벤트, 스토리지, 네트워크 통신 같은 기능은 모두 웹 API 위에서 동작합니다.
9장 웹 API와 브라우저 기능의 시작점은 서버 통신의 핵심인 AJAX와 Fetch API입니다. 사용자 경험을 부드럽게 만드는 가장 중요한 기술 중 하나입니다.
웹 페이지는 처음 로드될 때 서버에서 HTML, CSS, JavaScript를 받아옵니다. 하지만 이후 상호작용에서는 페이지 전체를 다시 받을 필요가 없는 경우가 많습니다. 필요한 데이터만 비동기로 주고받는 방식이 바로 AJAX입니다.
AJAX는 이름에 XML이 들어가지만, 실무에서는 JSON 같은 형식을 더 자주 사용합니다. 핵심은 전체 새로고침 없이 필요한 데이터만 교환한다는 점입니다.
과거에는 XMLHttpRequest가 중심이었고,
현재는 Promise 기반의 Fetch API가 기본 선택지가 되었습니다.
실습 API 준비 (로컬 Mock 서버)
외부 공개 API 상태에 영향받지 않도록, 이 절의 실습 URL은 http://localhost:4000 기준으로 맞춥니다.
포트 정책은 4000=Mock API, 4100=별도 백엔드/소켓/GraphQL로 분리해 두면 트랙 병행 학습 시 충돌을 줄일 수 있습니다.
npx json-server --watch db.json --port 4000db.json에는 최소한 posts, users 배열을 준비해 두면 예제를 그대로 재현할 수 있습니다.
XMLHttpRequest (XHR)
XMLHttpRequest 객체는 AJAX의 전통적인 구현체입니다. 복잡하고 콜백 지옥에 빠지기 쉬웠지만, 웹 개발의 중요한 한 획을 그었습니다.
// // fetch API가 없을 때의 XMLHttpRequest 사용 예시 (간략)
// function fetchDataXHR() {
// const xhr = new XMLHttpRequest();
// xhr.open('GET', 'http://localhost:4000/posts/1'); // 요청 초기화
// xhr.onload = function() { // 응답이 로드되었을 때 (성공)
// if (xhr.status >= 200 && xhr.status < 300) {
// console.log("XHR 응답 성공:", JSON.parse(xhr.responseText));
// } else {
// console.error("XHR 응답 실패:", xhr.status);
// }
// };
// xhr.onerror = function() { // 요청 실패 (네트워크 오류 등)
// console.error("XHR 네트워크 오류 발생");
// };
// xhr.send(); // 요청 전송
// }
// fetchDataXHR();XMLHttpRequest는 상태 변화에 따른 이벤트 리스너를 등록하고, 성공/실패 여부를 직접 판단해야 하는 등 다소 번거로운 점이 있었습니다.
Fetch API: AJAX의 현대적인 표준
Fetch API는 Promise를 기반으로 비동기 네트워크 요청을 수행하는 현대적인 인터페이스입니다. XMLHttpRequest보다 사용하기 쉽고, 더 강력하며, 더 많은 기능을 제공합니다.
fetch() 함수의 기본 사용법
fetch() 함수는 첫 번째 인자로 요청할 URL을 받으며, Promise를 반환합니다. 이 Promise는 네트워크 요청에 대한 Response 객체로 이행(resolve)됩니다. Response 객체에는 HTTP 응답에 대한 정보(상태 코드, 헤더 등)가 담겨 있습니다. 실제 응답 본문(body)의 데이터(JSON, 텍스트 등)를 얻으려면 Response 객체의 메서드를 추가로 호출해야 합니다.
// GET 요청 예시
fetch('http://localhost:4000/posts/1') // Promise 반환
.then(response => {
// 응답 상태 확인: response.ok는 200-299 상태 코드를 나타냄
if (!response.ok) {
throw new Error(`HTTP 오류! 상태: ${response.status}`);
}
// 응답 본문을 JSON으로 파싱 (Promise 반환)
return response.json();
})
.then(data => {
console.log("Fetch GET 응답 데이터:", data);
})
.catch(error => {
console.error("Fetch 요청 실패:", error);
});주의: fetch()는 네트워크 오류와 같이 실제 요청이 실패했을 때만 Promise를 거부(reject)합니다. HTTP 상태 코드 404(Not Found)나 500(Server Error)과 같은 서버 응답 오류는 Promise를 거부하지 않고, Response 객체의 ok 프로퍼티가 false로 설정됩니다. 따라서 항상 response.ok를 확인하여 HTTP 오류를 처리해야 합니다.
다양한 HTTP 메서드와 옵션
fetch() 함수의 두 번째 인자로 init 객체를 전달하여 다양한 요청 옵션을 설정할 수 있습니다.
// POST 요청 예시
const newPost = {
title: '새로운 게시물',
body: '이것은 Fetch API로 작성된 새로운 게시물 내용입니다.',
userId: 1,
};
fetch('http://localhost:4000/posts', {
method: 'POST', // HTTP 메서드 지정
headers: {
'Content-Type': 'application/json; charset=UTF-8', // 요청 헤더 지정
},
body: JSON.stringify(newPost), // 전송할 데이터 (JSON.stringify로 문자열화)
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP 오류! 상태: ${response.status}`);
}
return response.json();
})
.then(data => {
console.log("Fetch POST 응답 데이터 (생성된 게시물):", data);
})
.catch(error => {
console.error("Fetch POST 요청 실패:", error);
});주요 init 객체 옵션
method:GET,POST,PUT,DELETE등 HTTP 메서드 (GET이 기본값).headers: 요청 헤더를 설정하는 객체 (Content-Type,Authorization등).body:POST,PUT등과 함께 전송할 데이터. 문자열(JSON.stringify),FormData객체 등이 될 수 있습니다.mode:cors(기본값),no-cors,same-origin. CORS 정책과 관련됩니다.cache:default,no-store,reload,force-cache등 캐시 정책.credentials:omit,same-origin,include. 인증 정보(쿠키, HTTP 인증) 전송 여부.referrer: 요청의 Referer 헤더.signal:AbortController와 함께 요청을 취소할 때 사용.
async/await와 함께 Fetch 사용하기
우리는 7장에서 async/await 문법을 학습했습니다. fetch() 함수가 Promise를 반환하므로, async/await와 함께 사용하면 비동기 코드를 동기 코드처럼 읽기 쉽게 작성할 수 있습니다.
async function fetchUserData(userId) {
try {
const response = await fetch(`http://localhost:4000/users/${userId}`);
if (!response.ok) {
throw new Error(`사용자 데이터를 불러오는 데 실패했습니다. ${response.status}`);
}
const userData = await response.json();
console.log("사용자 데이터:", userData);
return userData;
} catch (error) {
console.error("데이터 로딩 중 오류 발생:", error);
return null;
}
}
fetchUserData(1); // ID가 1인 사용자 데이터 가져오기
fetchUserData(999); // 존재하지 않는 사용자 (오류 처리 테스트)async/await를 사용하면 then().catch() 체인보다 에러 처리 및 코드 흐름을 파악하기 훨씬 용이합니다.
요청 취소: AbortController
Fetch 요청은 한 번 시작되면 취소하기 어려웠습니다. AbortController는 Fetch 요청을 취소할 수 있는 메커니즘을 제공합니다. 이는 사용자가 페이지를 떠나거나, 검색어가 바뀌어 이전 요청이 불필요해질 때 유용합니다.
const controller = new AbortController();
const signal = controller.signal;
async function fetchWithCancellation(url) {
try {
const response = await fetch(url, { signal });
if (!response.ok) {
throw new Error(`HTTP 오류! 상태: ${response.status}`);
}
const data = await response.json();
console.log("데이터 로드 성공:", data);
return data;
} catch (error) {
if (error.name === 'AbortError') {
console.log("Fetch 요청이 취소되었습니다.");
} else {
console.error("Fetch 요청 오류:", error);
}
return null;
}
}
// 5초 후에 요청을 취소하는 예시
const longRunningFetch = fetchWithCancellation('http://localhost:4000/posts/1');
setTimeout(() => {
controller.abort(); // 요청 취소
}, 50); // 아주 짧은 시간 후에 취소하여 AbortError 발생 유도signal 옵션에 AbortController의 signal 프로퍼티를 전달하고, 필요할 때 controller.abort()를 호출하면 해당 요청이 취소됩니다.
CORS (Cross-Origin Resource Sharing)
Fetch API를 사용해 다른 도메인의 리소스에 접근할 때는 CORS (Cross-Origin Resource Sharing) 정책을 이해해야 합니다.
웹 브라우저는 보안상의 이유로 Same-Origin Policy (동일 출처 정책)을 따르며, 한 출처(Origin: 프로토콜, 호스트, 포트가 모두 동일한 경우)에서 로드된 페이지가 다른 출처 리소스에 접근하는 것을 제한합니다.
- 동일 출처:
http://example.com:8080/path와http://example.com:8080/anotherpath는 동일 출처. -
다른 출처
http://sub.example.com(다른 호스트)https://example.com(다른 프로토콜)http://example.com:3000(다른 포트)
다른 출처의 리소스에 접근하려면 서버에서 CORS 관련 HTTP 헤더(Access-Control-Allow-Origin 등)를 적절히 설정해 요청을 허용해야 합니다.
Fetch 요청의 mode 옵션을 cors(기본값) 또는 no-cors 등으로 설정해 브라우저 동작을 제어할 수 있으며, no-cors는 요청 전송은 허용하지만 스크립트의 응답 본문 접근은 제한합니다.
AJAX·Fetch 정리
이 절에서는 요청-응답 흐름을 오류 처리까지 포함해 재현 가능한 형태로 정리합니다. 이번 장에서는 웹 애플리케이션에서 서버와 비동기적으로 데이터를 주고받는 핵심 기술인 AJAX의 개념과, 그 현대적인 구현 표준인 Fetch API에 대해 심도 있게 학습했습니다.
여러분은 XMLHttpRequest의 간략한 역사와 함께, Promise 기반으로 동작하는 fetch() 함수의 기본 사용법을 익혔습니다. 특히 fetch()가 네트워크 오류가 아닌 HTTP 응답 오류에 대해서는 Promise를 거부하지 않는다는 점과 response.ok를 통해 응답 상태를 확인해야 한다는 중요한 점을 기억해야 합니다. 또한, method, headers, body와 같은 다양한 요청 옵션을 설정하는 방법과, async/await를 사용하여 비동기 코드를 더 간결하게 작성하는 방법, 그리고 AbortController를 이용해 요청을 취소하는 방법까지 살펴보았습니다. 마지막으로, 웹 통신에서 중요한 보안 정책인 CORS에 대해서도 간략하게 이해했습니다.
Fetch API는 모던 웹 애플리케이션 개발에서 서버 통신의 핵심 도구입니다. 이 장의 내용을 바탕으로 여러분은 동적이고 상호작용적인 웹 페이지를 구현하는 데 필요한 강력한 기술을 습득했습니다. 로컬 Mock API 또는 팀의 스테이징 API를 대상으로 직접 fetch() 요청을 보내보고, 받은 데이터를 웹 페이지에 표시하는 연습을 반복해 검증하는 것이 좋습니다.