WeniVooks

검색

JavaScript 에센셜

AJAX

1. AJAX의 탄생과 역사

1990년대 후반, OS 시장의 절대 강자 마이크로스프트(MS)는 Internet Explorer3를 만들 때 즈음, Windows OS에 Java 기술을 통합하고 있었습니다. 이 과정에서 Java에 대한 권리를 보유한 썬마이크로시스템즈와 법적 분쟁이 발생했습니다. 썬마이크로시스템즈는 MS가 Java를 무단으로 사용해 이득을 취했던 MS를 라이선스 계약 위반으로 고소합니다.

썬마이크로시스템즈의 로고. 소프트웨어 시장의 숨은 강자였으나 닷컴버블의 여파를 피하지 못하고 오라클에 인수 합병되어 사라졌습니다.

이 법적 분쟁의 결과로 MS가 패소하게 되면서, MS는 Windows에서 Java와 관련된 기능을 제거해야 했습니다. 이때 Java에 의존하던 여러 애플리케이션에 영향을 미쳤는데, 그 중 하나가 바로 이메일을 보내기 위한 애플리케이션인 이었습니다.

마이크로소프트 아웃룩. 이메일 전송과 사용자 일정관리에 특화된 앱입니다.

MS는 Java 기능을 대체할 수 있는 기술을 찾아야 했으며, JavaScript를 이용한 새로운 기술을 개발합니다. 이때 탄생한 것이 바로 ActiveXObject 입니다.

이 객체의 주요 기능이 바로 JS 코드를 통해 서버와 HTTP 통신 을 할 수 있다는 것이었습니다. 즉, JS로 HTTP 요청을 보내고 응답을 받을 수 있었습니다.

ActiveXObject의 등장으로 브라우저는 이전처러 페이지 전체를 새로고침하지 않고도 HTTP 요청을 보내 서버와 데이터를 주고받고 화면을 갱신할 수 있게 되었습니다. 이는 웹 애플리케이션 개발에 큰 변화를 가져왔습니다. 대표적으로 한 번에 수백명에게 이메일을 보낼 수 있는 최초의 서비스인 Gmail, 실시간으로 지도를 보여주는 구글맵 등이 등장하게 되었습니다.

시간이 지나 ActiveXObject는 XMLHttpRequest라는 이름으로 표준화되었고, 2005년에 제시 제임스 게럿(Jesse James Garrett)이라는 개발자가 JS 기술을 이용해 비동기적으로 서버와 통신할 수 있는 기술을 'AJAX'라고 통칭하여 부르기 시작했습니다.

AJAX는 Asynchronous JavaScript and XML의 약자로, 비동기적으로 서버와 통신할 수 있는 기술을 의미합니다. 이후 AJAX는 웹 애플리케이션 개발에 있어 필수적인 기술로 자리 잡게 되었습니다.

AJAX: A New Approach to Web Applications

AJAX 이전의 서버 통신
AJAX가 나오기 이전에 클라이언트가 데이터를 서버에 요청하는 방법은 브라우저 주소창에 특정 URL을 입력하거나, HTML 요소인 혹은 을 이용하는것 이었습니다.

  1. 서버는 요청 받은 데이터가 포함되어 있는 HTML 파일을 같이 전송합니다. 필요한 데이터만 받아오는 AJAX에 비효율적이었습니다. 마치 작고 귀여운 아기 벨로시랩터 레고가 가지고 싶다면 5만원짜리 세트로 구입해야만 하는것과 비슷합니다

  2. HTML 파일 전체가 교체되는 방식이기 때문에 화면이 렌더링되는 과정에서 화면이 깜빡이는 현상도 함께 나타났습니다.

2. XMLHttpRequest

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

2.1. 기본 사용법

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

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

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

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

readyState와 status

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

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

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

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

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

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

<button id="fetchData">Get Data</button>
 
<script>
  document.getElementById('fetchData').addEventListener('click', function () {
    const xhr = new XMLHttpRequest();
    const url = 'https://test.api.weniv.co.kr/mall';
 
    xhr.open('GET', url);
 
    xhr.onreadystatechange = function () {
      const resultDiv = document.getElementById('result');
 
      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 response = JSON.parse(xhr.responseText);
          console.log('데이터 수신 완료!');
          console.log('응답 데이터: ', response);
        } else {
          console.log('오류 발생: ' + xhr.status);
        }
      }
    };
 
    xhr.send();
  });
</script>
<button id="fetchData">Get Data</button>
 
<script>
  document.getElementById('fetchData').addEventListener('click', function () {
    const xhr = new XMLHttpRequest();
    const url = 'https://test.api.weniv.co.kr/mall';
 
    xhr.open('GET', url);
 
    xhr.onreadystatechange = function () {
      const resultDiv = document.getElementById('result');
 
      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 response = JSON.parse(xhr.responseText);
          console.log('데이터 수신 완료!');
          console.log('응답 데이터: ', response);
        } else {
          console.log('오류 발생: ' + xhr.status);
        }
      }
    };
 
    xhr.send();
  });
</script>
12장 비동기 프로그래밍12.2 동기와 비동기