yngmanie 블로그

MDN AJAX는 무엇인가?

해당 포스트는 MDN What’s AJAX?를 번역하여 작성하였습니다. 잘못된 부분이 있다면 댓글 부탁드립니다.

이 글은 기본적인 설명과 2개의 예제를 통해 AJAX에 대한 가이드를 전달드립니다.

AJAX란?

AJAX는 Asynchronous JavaScript And Xml의 약자입니다. 간단하게 말혀자면 서버와 통신하기위해 XMLHttpRequest 객체를 사용법입니다. JSON, XML, HTML, text 파일과 같이 다양한 포맷으로 정보를 주고 받을 수 있습니다. AJAX의 가장 매력적인 특징은 비동기입니다. 즉, 화면 깜박임없이 서버와 통신하고 데이터를 주고 받고 페이지를 업데이트할 수 있습니다.

아래 AJAX의 주요한 2가지 특징이 있습니다.

  • 페이지 리로딩없이 서버에 리퀘스트를 날릴 수 있습니다.
  • 서버로부터 데이터를 받을 수 있습니다.

step1. 어떻게 Http request를 만들 수 있나요?

Javascript를 활용해서 서버에 보내는 Http request 만들기 위해서는 이에 필요한 기능이 있는 객체 인스턴스가 필요합니다. 그것은 XMLHttpRequest에 있습니다. XMLHttpRequest는 IE의 XMLHTTP라는 ActiveX 객체였습니다. 그 후 Mozilla, Safari, 다른 브라우져들이 마이크로소프트의 ActiveX 객체의 메소드와 프로퍼티를 지원하는 XMLHttpRequest 객체를 구현했습니다. 또한 마이크로소프는 XMLHttpRequest 객체를 구현했습니다.

// 더 이상 필요없는 이전의 호환성 코드
if(window.XMLHttpRequest) { // Mozilla, Safari, IE7+ ...
  httpRequest = New XMLHttpRequest();
} else if (window.ActiveXObject) { // IE6를 포함한 이전 버전
  httpRequest = new ActiveXObject('Microsoft.XMLHTTP');
}

주의: 위에 예시는 XMLHttp 인스턴스를 생성하기위해 코드를 간략하게 표기한 것입니다. 좀 더 현실적인 예시가 궁금하면 step3의 글을 참조하시면 됩니다.

요청을 한 후 응답을 받게 됩니다. 이 단계에서 XMLHttp request 객체에게 Javascript 함수로 응답을 처리할 것이라고 알려합니다. 아래와 같이 객체의 onreadystateschange 프로퍼티를 세팅하고 요청이 상태를 바꿀 때 호출할 함수의 이름을 지정하면 됩니다.

httpRequest.onreadystatechange = nameOfTheFunction;

함수의 이름 다음에는 괄호나 함수의 이름이 없습니다. 왜냐하면 실제로 그것을 호출하는 것이 아니라 함수에대한 참조를 할당하는 것이기 때문입니다. 함수의 이름을 설정한 것 대신에 아래와 같이 익명함수를 사용해서 응답에대한 처리를 정의할 수 있습니다.

httpRequest.onreadystatechange = function() {
  // 서버 응답에 대한 코드를 여기에 작성
};

다음에, 응답을 받았을 때 처리할 코드를 작성한 후 실제로 요청을 해야합니다. 아래와 같이 HTTP request의 open()과 send() 메소드를 호출하면 됩니다.

httpRequest.open("Get", "http://www.example.org/some.file", ture);
httpRequest.send();
  • open()의 첫번 째 파라미터는 HTTP 요청 메소드 입니다. 메소드 종류는 GET, POST, HEAD 혹은 서버에서 지원하는 다른 메소드입니다. 모든 메소드는 대문자로 작성해야합니다. 그렇지 않으면 Firefox와 같은 특정 브라우저에서 요청을 보낼 수 없습니다. HTTP 요청 메소드에 대해서 좀더 많은 정보는 W3C Specs를 참고 부탁드립니다.

  • 두 번째 파라미터는 요청을 보내는 URL입니다. 보안을 위해 3rd-party 도메인으로 요청할 수 없습니다. 정확한 도메인으로 사용해야합니다. 그렇지 않으면 open()을 호출할 때 “permission denied” 에러가 발생할 수 있습니다. 일반적인 함정은 domain.tld으로 사이트를 접근하지만 www.domain.tld으로 페이지 호출을 시도하는 것입니다. 만약 다른 도메인으로 요청을 보낼 필요가 있다면 HTTP access control (CORS)을 참조 부탁드립니다.

  • 세번 째 파라미터는 요청이 비동기인지 아닌지 설정하는 것이며 옵션입니다. 만약 true(기본 값)으로 설정한다면 Javascript 실행은 계속되고 사용자는 서버에서 응답이 안온 상태에서 페이지에서 인터렉션이 가능합니다. 이것이 AJAX의 A에 해당합니다.

send() 메소드의 파라미터는 POST의 경우 서버에 보내고 싶은 모든 데이터도 될 수 있습니다. 데이터 형태는 query string처럼 서버에서 읽을 수 있는 형태여야 합니다.

"name=value&anothername=" + encodeURIComponent(myVar) + "&so=on";

multipart/form-data, JSON, XML와 같은 다른 포멧을 사용할 수 있습니다.

만약 POST를 사용해 데이터 전송을 원한다면 요청 형태를 MINE으로 세팅해야합니다. 예를 들어 send() 메소드를 호출하기 전에 query string을 아래와 같이 전달해야합니다.

httpRequest.setRequestHeader(
  "Content-type",
  "application/x-www-form-urlencoded"
);

step2. 서버 응답 핸들링

요청을 보냈을 때, 응답을 핸들링할 Javascript 함수의 이름을 제공했습니다.

httpRequest.onreadystatechange = nameOfTheFunction;

이 함수는 무슨 역할을 할까요? 함수는 요청 상태를 체크할 필요가 있습니다. 상태가 응답이 4로 시작하는 XMLHttpRequest.DONE 값을 가지고 있다면 이 의미는 모든 서버응답을 받았고 계속해서 프로세스를 진행하는 것에 문제가 없다는 뜻입니다.

if (httpRequest.readyState === XMLHttpRequest.DONE) {
  // 응답을 받았고 문제가 없습니다.
} else {
  // 아직 준비가 되지 않았습니다.
}

readyState의 모든 리스트는 XMLHTTPRequest.readyState에 있으며 아래와 같습니다.

  • 0: 요청이 초기화되지 않음
  • 1: 로딩중 혹은 서버와 연결 됨
  • 2: 로드됨 혹은 요청을 전달 받음
  • 3: 상호작용 혹은 요청을 처리 중
  • 4: 완려됨 혹은 요청은 끝났고 응답할 준비가 된 상태

다음 HTTP 응답의 HTTP response status codes 체크해 보세요. 가능한 코드는 W3C에 있습니다. 다음의 예시로 200 ok 응답코드로 AJAX 호출의 성공과 실패에 관련되 코드를 분기할 수 있습니다.

if (httpRequest.status === 200) {
  // 성공!
} else {
  // 요청에 문제가 있습니다.
  // 예를 들어, 응답 코드가 404(not found)이거나 500(internal server error)입니다.
}

응답에 대한 HTTP 상태 코드와 요청 상태를 체크한 후에 서버에 당신이 원하는 데이터를 전달할 수 있습니다. 데이터를 보내는 2가지 옵션이 있습니다.

  • httpRequest.responseText - 텍스트 문자열로 서버 응답을 리턴 받습니다.
  • httpRequest.responseXML - Javascript DOM 함수를 사용할 수 있는 XMLDocument 객체로 리턴 받습니다.

오직 비동기로 요청을 했을때만 위에 언급한 내용이 유효합니다. 만약 동기로 요청을 한다면 함수를 지정할 필요가 없고 이것은 UX에 치명적입니다.

step3. 간단한 예제

간단한 HTTP 요청으로 모든 것을 같이 합시다. 우리의 Javascript는 HTML 파일을 요청합니다. “i am test”라는 텍스트가 포함된 test.html파일 입니다. 그 후 우리는 alert()으로 응답을 표시합니다. 해당 코드는 jQuery가 제외된 vanilla Javascript입니다. 또한 HTML, XML, PHP 파일이 동일한 디렉토리에 위치해 있습니다.

<button id="ajaxButton" type="button">요청하다</button>

<script>
  (function() {
    var httpRequest;
    document.getElementById("ajaxButton").addEventListener('click', makeRequest);

    fucntion makeRequest() {
      httpRequest = new XMLHttpRequest();

      if(!httpRequest) {
        alert("포기... XMLHTTP 인스턴스를 생성할 수 없습니다.");
        return flase;
      }
      httpRequest.onreadystatechange = alertContents;
      httpRequest.open("GET", "test.html");
      httpRequest.send();
    }

    function alertContents() {
      if(httpRequest.readyState === XMLHttpRequest..DONE) {
        if(httpRequest.status === 200) {
          alert(httpRequest.responseText);
        } else {
          alert("요청에 문제가 있습니다.")
        }
      }
    }
  }) ();
</script>
  • 사용자가 “요청하다” 버튼을 클릭합니다.
  • 이벤트 핸들러는 makeRequest()를 호출합니다.
  • 요청이 실행되면 onreadystatechange이 실행되고 alertContents으로 전달됩니다.
  • alertContents()는 응답이 OK인지 체크하고 test.html 파일의 내용을 alert()합니다.

주의 1: 만약 요청에 대한 응답으로 HTML파일이 아니라 XML 파일이라면 반드시 응답 헤더가 IE에서 작동할 수 있도록 설정해야합니다. 헤더에 ‘Content-Type: application/xml’라고 설정을 안하면 IE는 XML 엘리먼트에 접근하려고 시도하는 라인 뒤에 “Object Expected”에러가 발생할 겁니다.

주의 2: 헤더에 ‘Cache-Control: no-cache’라고 설정하지 않으면 브라우저는 응답을 캐시하고 요청을 다시 보내지않아 디버깅하기 어려울 수 있습니다. 그리고 매번 타임스템프나 랜덤 숫자와 같이 다른 GET 파라미터를 추가할 수 있습니다.(bypassing the cache)

주의 3: httpRequest를 전역변수로 사용한다면 makeRequest()를 호출하는 함수끼리 경재을 서로를 덮어씌울 수 있습니다. 이를 피하기위해서는 AJAX 함수가 포함된 클로저에 httpRequest를 지역변수로 선언해야합니다.

서버 다운과 같이 통신 오류는 응답 상태에 접근할 때 onreadystatechange에서 예외가 발생합니다. 이러한 문제를 완하시키기 위해서 try…catch문안에 if…then 구문을 감쌀 수 있습니다.

function alertContents() {
  try {
    if (httpRequest.readyState === XMLHttpRequest.DONE) {
      if (httpRequest.status === 200) {
        alert(httpRequest.responseText);
      } else {
        alert("요청에 문제가 있습니다.");
      }
    }
  } catch (e) {
    alert("예외가 발생했습니다. 이유는: " + e.description);
  }
}

step4. XML 응답 작업

이전에 예제에서 HTTP request 응답을 받은 후 우리는 test.html 파일의 내용이 포함된 요청 객체의 responseText 프로퍼티를 사용했습니다. 이번에는 responsseXML 프로퍼티를 사용하려고 합니다.

우선 우리나 나중에 요청할 유효한 XML 도규먼트를 생성합니다. test.XML이며 내용은 하기와 같습니다.

<?xml version="1.0"?>
<root>
  테스트 입니다.
</root>

우리는 스크립트 안에서 요청라인만 변경합니다.

onclick = "makeRequest('test.xml')">

그런 다음 alertContents()안에서 alert(httpRequest.responseText)를 아래와 같이 변경해야합니다.

var xmldoc = httpRequest.responseXML;
var root_node = xmldoc.getElementByTagName("root").item(0);
alert(root_node.firstChild.data);

이 코드는 responseXML에 의해 주어준 XMLDocument 객체를 취하고 XML 도규먼트안에 있는 데이터에 접근하기위해 DOM 메소드를 사용합니다.

step5. 데이터로 작업하기

마지막으로 데이터를 서버에 전달하고 응답을 받아 봅시다. Javascript는 test.php라는 동적 페이지를 요청할 겁니다. 우리가 보낸 데이터를 가져와 alert()으로 “computed” 문자열으로 리턴할 겁니다.

먼저 HTML 텍스트박스를 추가하고 유저가 그들의 이름을 작성할 수 있도록 합니다.

<label>
  이름을 입력해주세요:
  <input type="text" id="ajaxTextbox" />
</label>
<span id="ajaxButton" style="cursor:pointer; text-decoration: underline">
  요청하기
</span>

텍스트 박스안에 있응 유저가 입력한 데이터를 얻고, 서버-사이드 스크립트와 함께 makeRequest() 함수로 보내기위해서는 이벤트 핸들러에 아래와 같이 라인을 추가해야합니다.

ducument.getElementById("ajaxButton").onclick = function() {
  var userName = document.getElementById("ajaxTextbox").value;
  makeRequest("test.php", userName);
};

서버에 유저 데이터를 보내기위해서는 makeRequest()를 수정해야합니다. 우선 요청 메소드를 GET에서 POST으로 변경하고 httpRequest.send()에 파마미터로 데이터를 추가합니다.

function makeRequest(url, userName) {

  ...

  httpRequest.onreadystatechange = alertContents;
  httpRequest.open('POST', url);
  httpRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
  httpRequest.send('userName=' + encodeURIComponent(userName));
}

서버에서 리턴하는 경우 alertContents()함수는 computed 문자열을 alert하기위해 Step3와 같이 작성할 수 있습니다. 하지만 서버가 computed 문자열과 오리지날 유저 데이터 모두를 리턴한다고 가정해 봅시다. 만약 유저가 텍스트박스에 ‘용민’이라고 작성했다면 서버는 아래와 같이 응답을 할 겁니다.

{"userData": "용민", "computedString": "Hi, 용민!"}

응답받은 데이터를 alertContents()에서 사용하기위해서는 responseText으로 할 수 없습니다. 우리는 그것을 파스해야하고 우리가 원하는 속성으로 computedString을 alert해야합니다.

function alertContents() {
  if (httpRequest.readyState === XMLHttpRequest.DONE) {
    if (httpRequest.status === 200) {
      var response = JSON.parse(httpRequest.responseText);
      alert(response.computedString);
    } else {
      alert("요청에 문제가 있습니다.");
    }
  }
}

test.php 파일은 아래와 같습니다.

$name = (isset($_POST['userName'])) ? $_POST['userName'] : 'no name';
$computedString = "Hi, " . $name . "!";
$array = ['userName' => $name, 'computedString' => $computedString];
echo json_encode($array);

좀 더 많은 DOM메소드가 궁금하다면 Mozilla’s DOM implementation을 찹조 부탁드립니다.