WeniVooks

검색

JavaScript 에센셜

XMLHttpRequest

1. XMLHttpRequest

XMLHttpRequest(XHR)는 서버와의 비동기 통신을 가능하게 하는 JavaScript 객체입니다. 이를 통해 웹 페이지의 전체를 새로고침하지 않고도 서버로부터 데이터를 받아올 수 있습니다.

1.1. 기본 사용법

XMLHttpRequest를 사용하는 기본적인 방법을 살펴보겠습니다. 아래 코드는 GET 방식으로 서버로 요청을 보내고, 응답을 받아 콘솔에 출력하는 코드입니다.

// XHR 객체를 생성합니다.
const xhr = new XMLHttpRequest();
 
// 요청을 초기화합니다. 통신 방법과 URL을 지정합니다.
xhr.open('GET', 'https://dev.wenivops.co.kr/services/fastapi-crud/1/product');
 
// 응답 상태가 변화할 때마다 호출되는 이벤트 리스너를 설정합니다.
xhr.onreadystatechange = function () {
  // readyState가 4(완료)이고 status가 200(성공)일 때 응답을 처리합니다.
  if (xhr.readyState === 4 && xhr.status === 200) {
    const response = JSON.parse(xhr.responseText);
    console.log(response);
  }
};
 
// 요청을 서버로 보냅니다.
// send 메소드가 실행되어야만 우리가 위에서 설정한 내용들이 의미를 가지게 됩니다.
xhr.send();
// XHR 객체를 생성합니다.
const xhr = new XMLHttpRequest();
 
// 요청을 초기화합니다. 통신 방법과 URL을 지정합니다.
xhr.open('GET', 'https://dev.wenivops.co.kr/services/fastapi-crud/1/product');
 
// 응답 상태가 변화할 때마다 호출되는 이벤트 리스너를 설정합니다.
xhr.onreadystatechange = function () {
  // readyState가 4(완료)이고 status가 200(성공)일 때 응답을 처리합니다.
  if (xhr.readyState === 4 && xhr.status === 200) {
    const response = JSON.parse(xhr.responseText);
    console.log(response);
  }
};
 
// 요청을 서버로 보냅니다.
// send 메소드가 실행되어야만 우리가 위에서 설정한 내용들이 의미를 가지게 됩니다.
xhr.send();
1.2. readyState

readyState 속성은 XHR 요청의 통신 상태 를 나타냅니다. 다음은 readyState의 값과 의미입니다.

의미
0 (UNSENT)XHR 객체가 생성되었지만 아직 초기화되지 않았습니다.
1 (OPENED)open()함수가 호출되어 요청이 초기화되었습니다.
2 (HEADERS_RECEIVED)send()함수가 호출되었습니다.
3 (LOADING)데이터를 다운받는 중 입니다.
4 (DONE)통신이 완료되었습니다.

onreadystatechange 이벤트 리스너를 통해 readyState가 변할 때마다 콜백 함수를 실행할 수 있습니다.

requestObj.onreadystatechange = () => {
  if (requestObj.readyState == 4 && requestObj.status == '200') {
    const result = JSON.parse(requestObj.responseText);
  }
};
requestObj.send();
requestObj.onreadystatechange = () => {
  if (requestObj.readyState == 4 && requestObj.status == '200') {
    const result = JSON.parse(requestObj.responseText);
  }
};
requestObj.send();
1.3. status 코드

status 속성은 서버의 응답 상태 를 나타냅니다.

의미
200요청이 성공적으로 완료되었습니다.
404요청한 리소스를 찾을 수 없습니다.
500서버 내부 오류가 발생했습니다.
서버 응답 상태 | MDN

readyState와 status

readyState와 status는 비슷해 보이지만 다른 속성을 나타냅니다. 간단한 비유로 설명해보겠습니다.

배가 너무 고파서 1시간 후에 집에서 저녁을 먹기 위해 피자를 주문해야 합니다. 그래서 배달앱을 꺼내 피자를 한판 주문합니다.

이때 배달하는 사람이 피자 배달을 완료했는지, 아니면 배달 중인지 추적합니다. (readyState)

피자가 집에 도착하고 피자가 올바르게 만들어졌는지 확인합니다. 피자가 타지 않았는지, 재료를 잘못 넣었는지, 또는 주문대로 내가 원하는 피자가 맞는지 확인합니다. (status)

결론적으로, readyState === 4 (즉, 피자가 집에 도착했음)이고 status === 200 (즉, 피자가 올바르게 만들어졌음)인 경우에만 모든 통신이 계획대로 잘 진행되었다는 것을 의미합니다.

실제 코드로 작성해봅시다. 버튼을 누르면 데이터를 비동기적으로 가져와 출력하는 앱을 만들어 보겠습니다.

<button id="fetchData">상품 목록 가져오기</button>
<div id="productList"></div>
 
<script>
  document.getElementById('fetchData').addEventListener('click', function () {
    const xhr = new XMLHttpRequest();
    const url = 'https://dev.wenivops.co.kr/services/fastapi-crud/1/product';
 
    xhr.open('GET', url);
 
    xhr.onreadystatechange = function () {
      if (xhr.readyState === 1) {
        console.log('연결이 열렸습니다...');
      } else if (xhr.readyState === 2) {
        console.log('요청이 서버로 전송되었습니다...');
      } else if (xhr.readyState === 3) {
        console.log('데이터를 다운로드 중입니다...');
      } else if (xhr.readyState === 4) {
        if (xhr.status === 200) {
          const products = JSON.parse(xhr.responseText);
          console.log('상품 목록 수신 완료!');
 
          // 상품 목록을 화면에 표시
          const productListDiv = document.getElementById('productList');
          productListDiv.innerHTML = '';
 
          products.forEach((product) => {
            const productCard = document.createElement('div');
            productCard.classList.add('product-card');
            productCard.innerHTML = `
              <h3>${product.productName}</h3>
              <p>가격: ${product.price}원</p>
              <p>재고: ${product.stockCount}개</p>
              <p>할인율: ${product.discountRate}%</p>
            `;
            productListDiv.appendChild(productCard);
          });
        } else {
          console.log('오류 발생: ' + xhr.status);
        }
      }
    };
 
    xhr.send();
  });
</script>
<button id="fetchData">상품 목록 가져오기</button>
<div id="productList"></div>
 
<script>
  document.getElementById('fetchData').addEventListener('click', function () {
    const xhr = new XMLHttpRequest();
    const url = 'https://dev.wenivops.co.kr/services/fastapi-crud/1/product';
 
    xhr.open('GET', url);
 
    xhr.onreadystatechange = function () {
      if (xhr.readyState === 1) {
        console.log('연결이 열렸습니다...');
      } else if (xhr.readyState === 2) {
        console.log('요청이 서버로 전송되었습니다...');
      } else if (xhr.readyState === 3) {
        console.log('데이터를 다운로드 중입니다...');
      } else if (xhr.readyState === 4) {
        if (xhr.status === 200) {
          const products = JSON.parse(xhr.responseText);
          console.log('상품 목록 수신 완료!');
 
          // 상품 목록을 화면에 표시
          const productListDiv = document.getElementById('productList');
          productListDiv.innerHTML = '';
 
          products.forEach((product) => {
            const productCard = document.createElement('div');
            productCard.classList.add('product-card');
            productCard.innerHTML = `
              <h3>${product.productName}</h3>
              <p>가격: ${product.price}원</p>
              <p>재고: ${product.stockCount}개</p>
              <p>할인율: ${product.discountRate}%</p>
            `;
            productListDiv.appendChild(productCard);
          });
        } else {
          console.log('오류 발생: ' + xhr.status);
        }
      }
    };
 
    xhr.send();
  });
</script>
1.4 콜백 함수

XMLHttpRequest는 비동기적으로 작동하기 때문에, 요청이 완료된 후에 실행할 콜백 함수를 설정해야 합니다. 위의 예제에서는 onreadystatechange 속성을 사용하여 상태가 변경될 때마다 호출되는 함수를 설정했습니다.

xhr.onreadystatechange = function () { if (xhr.readyState === 4 && xhr.status === 200) { const response = xhr.responseText; console.log(response); } };

이렇게 설정된 콜백 함수는 요청이 완료되었을 때 자동으로 호출됩니다. 이때 xhr 객체를 통해 서버의 응답을 처리할 수 있습니다.

예를 들어, Weniv API를 사용하여 상품 목록을 가져온 후, 특정 상품의 상세 정보를 가져오고, 그 상품을 수정하는 작업을 순차적으로 처리해야 한다면 다음과 같은 코드가 될 수 있습니다:

const xhr1 = new XMLHttpRequest(); xhr1.open('GET', 'https://dev.wenivops.co.kr/services/fastapi-crud/1/product'); xhr1.onreadystatechange = function() { if (xhr1.readyState === 4 && xhr1.status === 200) { const products = JSON.parse(xhr1.responseText); console.log('상품 목록:', products); if(products.length > 0) { // 첫 번째 상품의 상세 정보 요청 const productId = products[0].id; const xhr2 = new XMLHttpRequest(); xhr2.open('GET', `https://dev.wenivops.co.kr/services/fastapi-crud/1/product/${productId}`); xhr2.onreadystatechange = function() { if (xhr2.readyState === 4 && xhr2.status === 200) { const productDetail = JSON.parse(xhr2.responseText); console.log('상품 상세 정보:', productDetail); // 해당 상품 정보 수정 const updatedProduct = { ...productDetail, price: productDetail.price + 1000 }; const xhr3 = new XMLHttpRequest(); xhr3.open('PUT', `https://dev.wenivops.co.kr/services/fastapi-crud/1/product/${productId}`); xhr3.setRequestHeader('Content-Type', 'application/json'); xhr3.onreadystatechange = function() { if (xhr3.readyState === 4) { if (xhr3.status === 200) { const response = JSON.parse(xhr3.responseText); console.log('상품 수정 결과:', response); } else { console.error('상품 수정 실패:', xhr3.status); } } }; xhr3.send(JSON.stringify(updatedProduct)); } else if (xhr2.readyState === 4) { console.error('상품 상세 정보 가져오기 실패:', xhr2.status); } }; xhr2.send(); } } else if (xhr1.readyState === 4) { console.error('상품 목록 가져오기 실패:', xhr1.status); } }; xhr1.send();

위 코드는 다음과 같은 문제점을 가집니다:

  1. 가독성 저하: 코드가 오른쪽으로 깊게 들여쓰기되어 읽기 어렵습니다.
  2. 유지보수 어려움: 코드 구조가 복잡해져 수정이나 확장이 어렵습니다.
  3. 에러 처리 복잡성: 각 요청마다 별도의 에러 처리가 필요하며, 일관된 에러 처리가 어렵습니다.
  4. 디버깅 어려움: 콜백 중첩으로 인해 문제 발생 시 디버깅이 복잡해집니다.
10.2 JSON10.4 fetch API