XMLHttpRequest: 완전 가이드 (2026)
XMLHttpRequest의 작동 방식과 Fetch 대신 이를 사용해야 하는 경우에 대해 알아보세요.
Hypereal로 구축 시작하기
단일 API를 통해 Kling, Flux, Sora, Veo 등에 액세스하세요. 무료 크레딧으로 시작하고 수백만으로 확장하세요.
신용카드 불필요 • 10만 명 이상의 개발자 • 엔터프라이즈 지원
XMLHttpRequest: 완벽 가이드 (2026)
XMLHttpRequest (XHR)는 브라우저에서 HTTP 요청을 만들기 위한 오리지널 JavaScript API입니다. 새로운 프로젝트에서는 Fetch API가 이를 대체하는 추세지만, XMLHttpRequest는 2026년 현재에도 특정 사례, 특히 업로드 진행률 추적, 레거시 브라우저 지원 및 기존 코드베이스 유지보수를 위해 여전히 중요하게 사용됩니다.
이 가이드는 XMLHttpRequest에 대해 알아야 할 모든 것(기본 사용법, 고급 기능, 에러 핸들링, 그리고 Fetch 대신 XHR을 선택해야 하는 시점)을 다룹니다.
XMLHttpRequest란 무엇인가요?
XMLHttpRequest는 페이지를 새로고침하지 않고도 HTTP 요청을 보내고 응답을 받을 수 있게 해주는 브라우저 내장 API입니다. 이름과 달리 XML뿐만 아니라 모든 데이터 형식을 처리할 수 있습니다. JSON, 일반 텍스트, HTML, 바이너리 데이터, 폼 데이터 모두 XHR로 작업 가능합니다.
const xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/data");
xhr.onload = function () {
console.log(JSON.parse(xhr.responseText));
};
xhr.send();
기본 GET 요청
가장 간단한 형태의 XHR GET 요청은 다음과 같습니다.
function fetchUsers() {
const xhr = new XMLHttpRequest();
// 요청 구성
xhr.open("GET", "https://api.example.com/v1/users");
// 응답 타입 설정 (선택 사항, 기본값은 text)
xhr.responseType = "json";
// 성공적인 응답 처리
xhr.onload = function () {
if (xhr.status === 200) {
console.log("Users:", xhr.response);
} else {
console.error("Error:", xhr.status, xhr.statusText);
}
};
// 네트워크 에러 처리
xhr.onerror = function () {
console.error("Network error occurred");
};
// 요청 전송
xhr.send();
}
fetchUsers();
XHR 라이프사이클
| 이벤트 | 발생 시점 |
|---|---|
loadstart |
요청 전송이 시작될 때 |
progress |
데이터를 수신하는 중 (주기적으로 발생) |
load |
요청이 성공적으로 완료되었을 때 |
error |
네트워크 에러 발생 시 (404와 같은 HTTP 에러 제외) |
abort |
xhr.abort()를 통해 요청이 취소되었을 때 |
timeout |
요청 시간이 초과되었을 때 |
loadend |
요청이 종료되었을 때 (load, error, abort 발생 후) |
JSON을 사용한 POST 요청
function createUser(userData) {
const xhr = new XMLHttpRequest();
xhr.open("POST", "https://api.example.com/v1/users");
// 헤더 설정
xhr.setRequestHeader("Content-Type", "application/json");
xhr.setRequestHeader("Authorization", "Bearer your_token_here");
xhr.responseType = "json";
xhr.onload = function () {
if (xhr.status === 201) {
console.log("User created:", xhr.response);
} else {
console.error("Failed:", xhr.status, xhr.response);
}
};
xhr.onerror = function () {
console.error("Network error");
};
// JSON 데이터 전송
xhr.send(JSON.stringify(userData));
}
createUser({
name: "Jane Smith",
email: "jane@example.com",
role: "developer",
});
PUT 및 DELETE 요청
// 리소스를 업데이트하기 위한 PUT 요청
function updateUser(id, data) {
const xhr = new XMLHttpRequest();
xhr.open("PUT", `https://api.example.com/v1/users/${id}`);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.responseType = "json";
xhr.onload = function () {
if (xhr.status === 200) {
console.log("Updated:", xhr.response);
}
};
xhr.send(JSON.stringify(data));
}
// 리소스를 삭제하기 위한 DELETE 요청
function deleteUser(id) {
const xhr = new XMLHttpRequest();
xhr.open("DELETE", `https://api.example.com/v1/users/${id}`);
xhr.setRequestHeader("Authorization", "Bearer your_token_here");
xhr.onload = function () {
if (xhr.status === 204) {
console.log("Deleted successfully");
}
};
xhr.send();
}
요청 헤더 설정하기
const xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/v1/data");
// 인증 (Authentication)
xhr.setRequestHeader("Authorization", "Bearer token123");
// API Key
xhr.setRequestHeader("X-API-Key", "sk_live_abc123");
// 컨텐츠 타입 (POST/PUT 요청 시)
xhr.setRequestHeader("Content-Type", "application/json");
// 커스텀 헤더
xhr.setRequestHeader("X-Request-ID", "req_abc123");
xhr.setRequestHeader("Accept-Language", "ko-KR");
xhr.send();
응답 헤더 읽기
xhr.onload = function () {
// 특정 헤더 가져오기
const contentType = xhr.getResponseHeader("Content-Type");
const rateLimit = xhr.getResponseHeader("X-RateLimit-Remaining");
// 모든 헤더를 문자열로 가져오기
const allHeaders = xhr.getAllResponseHeaders();
console.log(allHeaders);
};
응답 타입 (Response Types)
XHR은 다양한 응답 타입을 지원합니다.
| responseType | xhr.response 타입 | 사용 사례 |
|---|---|---|
"" (기본값) |
String | 일반 텍스트, HTML |
"text" |
String | 기본값과 동일 |
"json" |
Object | API 응답 |
"blob" |
Blob | 이미지, 파일 |
"arraybuffer" |
ArrayBuffer | 이진(Binary) 데이터 |
"document" |
Document | XML/HTML 파싱 |
// JSON 응답
const xhr1 = new XMLHttpRequest();
xhr1.responseType = "json";
xhr1.onload = () => console.log(xhr1.response.name); // 객체에 직접 접근
// Blob 응답 (파일 다운로드용)
const xhr2 = new XMLHttpRequest();
xhr2.responseType = "blob";
xhr2.onload = () => {
const url = URL.createObjectURL(xhr2.response);
const a = document.createElement("a");
a.href = url;
a.download = "file.pdf";
a.click();
URL.revokeObjectURL(url);
};
// ArrayBuffer (이진 데이터 처리용)
const xhr3 = new XMLHttpRequest();
xhr3.responseType = "arraybuffer";
xhr3.onload = () => {
const data = new Uint8Array(xhr3.response);
console.log("수신된 바이트 수:", data.length);
};
업로드 진행률 추적
이 부분은 XHR이 Fetch API보다 뛰어난 강점을 보이는 지점입니다. XHR은 세밀한 업로드 및 다운로드 진행 이벤트를 제공합니다.
function uploadFile(file) {
const xhr = new XMLHttpRequest();
const formData = new FormData();
formData.append("file", file);
// 업로드 진행률
xhr.upload.onprogress = function (event) {
if (event.lengthComputable) {
const percentComplete = Math.round((event.loaded / event.total) * 100);
console.log(`업로드 진행률: ${percentComplete}%`);
updateProgressBar(percentComplete);
}
};
xhr.upload.onload = function () {
console.log("업로드 완료!");
};
// 다운로드 진행률 (응답 수신 시)
xhr.onprogress = function (event) {
if (event.lengthComputable) {
const percent = Math.round((event.loaded / event.total) * 100);
console.log(`다운로드 진행률: ${percent}%`);
}
};
xhr.onload = function () {
if (xhr.status === 200) {
console.log("서버 응답:", JSON.parse(xhr.responseText));
}
};
xhr.open("POST", "https://api.example.com/v1/upload");
xhr.send(formData);
}
// 파일 입력 요소와 함께 사용
document.getElementById("fileInput").addEventListener("change", (e) => {
uploadFile(e.target.files[0]);
});
진행 바(Progress Bar) HTML
<input type="file" id="fileInput" />
<div class="progress-bar">
<div class="progress-fill" id="progressFill"></div>
</div>
<span id="progressText">0%</span>
<script>
function updateProgressBar(percent) {
document.getElementById("progressFill").style.width = percent + "%";
document.getElementById("progressText").textContent = percent + "%";
}
</script>
타임아웃 및 요청 취소
타임아웃 설정
const xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/v1/slow-endpoint");
// 타임아웃을 10초로 설정
xhr.timeout = 10000;
xhr.ontimeout = function () {
console.error("10초 후 요청 시간이 초과되었습니다.");
};
xhr.onload = function () {
console.log("응답 수신:", xhr.response);
};
xhr.send();
요청 취소하기
const xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/v1/large-data");
xhr.onabort = function () {
console.log("요청이 취소되었습니다.");
};
xhr.send();
// 3초 후 취소
setTimeout(() => {
xhr.abort();
}, 3000);
에러 핸들링
완성된 에러 핸들링 패턴은 다음과 같습니다.
function apiRequest(method, url, data = null) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(method, url);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.responseType = "json";
xhr.timeout = 15000;
xhr.onload = function () {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response);
} else {
reject({
status: xhr.status,
statusText: xhr.statusText,
response: xhr.response,
});
}
};
xhr.onerror = function () {
reject({ status: 0, statusText: "네트워크 에러", response: null });
};
xhr.ontimeout = function () {
reject({ status: 0, statusText: "시간 초과", response: null });
};
xhr.onabort = function () {
reject({ status: 0, statusText: "취소됨", response: null });
};
xhr.send(data ? JSON.stringify(data) : null);
});
}
// async/await와 함께 사용
async function getUsers() {
try {
const users = await apiRequest("GET", "https://api.example.com/v1/users");
console.log(users);
} catch (error) {
if (error.status === 401) {
console.log("다시 로그인해 주세요.");
} else if (error.status === 0) {
console.log("네트워크 문제:", error.statusText);
} else {
console.log("API 에러:", error.status, error.response);
}
}
}
ReadyState 값
XHR은 readyState를 통해 요청의 라이프사이클을 추적합니다.
| 값 | 상수 | 의미 |
|---|---|---|
| 0 | UNSENT |
open()이 아직 호출되지 않음 |
| 1 | OPENED |
open() 호출됨 |
| 2 | HEADERS_RECEIVED |
응답 헤더 수신됨 |
| 3 | LOADING |
응답 본문 로딩 중 |
| 4 | DONE |
요청 완료 |
xhr.onreadystatechange = function () {
switch (xhr.readyState) {
case XMLHttpRequest.OPENED:
console.log("요청이 열렸습니다.");
break;
case XMLHttpRequest.HEADERS_RECEIVED:
console.log("헤더 수신:", xhr.getAllResponseHeaders());
break;
case XMLHttpRequest.LOADING:
console.log("데이터 로딩 중...");
break;
case XMLHttpRequest.DONE:
console.log("요청 완료, 상태 코드:", xhr.status);
break;
}
};
XMLHttpRequest vs Fetch API
| 기능 | XMLHttpRequest | Fetch API |
|---|---|---|
| 문법 | 콜백/이벤트 기반 | 프로미스(Promise) 기반 |
| 업로드 진행률 | 지원 (xhr.upload.onprogress) |
네이티브 지원 없음 |
| 다운로드 진행률 | 지원 (xhr.onprogress) |
지원 (ReadableStream 경유) |
| 취소 | xhr.abort() |
AbortController |
| 타임아웃 | 내장 (xhr.timeout) |
AbortController를 이용한 수동 설정 |
| 스트리밍 | 지원하지 않음 | 지원 |
| 서비스 워커 | 지원하지 않음 | 지원 |
| CORS | 동일한 동작 | 동일한 동작 |
| 요청/응답 객체 | 없음 | 있음 (재사용 가능) |
XMLHttpRequest를 사용해야 할 때
- 업로드 진행률 추적이 필요할 때 (진행 바가 있는 파일 업로드)
- 이미 XHR을 사용 중인 레거시 코드를 작업할 때
- 별도의 추가 코드 없이 내장 타임아웃 기능이 필요할 때
Fetch를 사용해야 할 때
- 새로운 프로젝트를 시작할 때 (Fetch의 API가 훨씬 깔끔함)
- 스트리밍 응답이 필요할 때 (Fetch는
ReadableStream지원) - **서비스 워커(Service Workers)**를 사용할 때 (Fetch가 유일한 옵션)
- 서버 사이드 JavaScript 환경일 때 (Node.js 18+에서 Fetch 사용 가능)
폼 데이터(Form Data) 전송
// FormData 사용 (파일 업로드 및 폼 제출용)
const formData = new FormData();
formData.append("name", "Jane");
formData.append("email", "jane@example.com");
formData.append("avatar", fileInput.files[0]);
const xhr = new XMLHttpRequest();
xhr.open("POST", "https://api.example.com/v1/users");
// Content-Type을 직접 설정하지 마세요. 브라우저가 boundary를 포함해 자동으로 설정합니다.
xhr.send(formData);
// URL-encoded 데이터 사용
const xhr2 = new XMLHttpRequest();
xhr2.open("POST", "https://api.example.com/v1/login");
xhr2.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr2.send("username=jane&password=secret123");
결론
XMLHttpRequest는 2026년에도 특히 업로드 진행률 추적과 레거시 코드베이스 환경에서 유능하고 유효한 API입니다. 새로운 프로젝트에서는 Fetch가 기본 선택이지만, 기존 코드를 유지보수하거나 업로드 진행률과 같은 XHR만의 고유한 기능이 필요한 시나리오를 위해 XHR을 이해하는 것은 매우 가치가 있습니다.
미디어 파일을 업로드하거나 생성 진행률을 표시해야 하는 웹 애플리케이션을 구축 중이라면, Hypereal AI는 진행률 추적이 가능한 AI 이미지 및 비디오 생성 API를 제공합니다. 이는 XMLHttpRequest의 진행 이벤트를 활용하기에 완벽한 사례입니다.
