Dandy Now!
  • [개발자의품격][부트캠프][1기][10차시] JavaScript 주요 포인트 #12 | 고급 문법 - XMLHttpRequest, Fetch API, Promise, Async/Await
    2022년 02월 09일 22시 12분 57초에 업로드 된 글입니다.
    작성자: DandyNow
    728x90
    반응형

    JavaScript 주요 포인트 #12

    자바스크립트는 AJAX(Asynchronous JavaScript and XML) 이전과 이후로 나뉜다. AJAX로 인해 비동기 통신 및 웹페이지의 특정 일부분만 랜더링 하는 것이 가능해졌다. 이로 인해 웹이 급속도록 발전하게 되었다. AJAX는 동적인 웹 페이지를 만들기 위해 사용한다.

     

    여기에서 다루게 될 내용은 XMLHttpRequest, Fetch API, Promise, Async/Await로서 자바스크립트 전체 문법을 통틀어 매우 중요한 부분이다. 이번 실습은 테스트 서버가 필요하다. 따라서 JSON Server를 실행한다.

     

    JSON Server 실행

    json-server --watch db.json

     


     

    XMLHttpRequest

    XMLHttpRequest(XHR) 객체는 서버와 상호작용하기 위해 사용한다. 지금은 XMLHttpRequest 객체를 직접적으로 사용할 일이 잘 없다. jQuery, fetch, axios을 사용하기 때문이다. 이 중에서도 axios를 주로 사용한다. 내부적으로 XMLHttpRequest가 돌아간다.

     

    HTTP method(HTTP 요청 방식)

    • GET - 리소스 요청
    • POST - 리소스 생성
    • PUT - 리소스 수정
    • PATCH - 리소스 일부 수정(거의 안 씀)
    • DELETE - 리소스 삭제

     

    JSON Server Resources

    • http://localhost:3000/posts
    • http://localhost:3000/comments
    • http://localhost:3000/profile

     

    아래 코드 블록은 XMLHttpRequest 객체를 이용하여 서버의 데이터를 조회, 생성, 수정, 삭제하는 코드이다. 여기에서는 HTTP method(HTTP 요청 방식)와 JSON Server Resources를 이용한다.

    <body>
      <button onclick="getData()">조회</button>
      <button onclick="postData()">생성</button>
      <button onclick="putData()">수정</button>
      <button onclick="deleteData()">삭제</button>
      <script>
        // 조회
        function getData() {
          const xhr = new XMLHttpRequest();
          xhr.open("GET", "http://localhost:3000/posts"); // 요청방식과 서버URL을 넣어준다. 준비 상태이다.
          xhr.setRequestHeader("content-type", "application/json"); // 요청에 맞는 헤더 값을 설정해야 한다. 서버와는 일반적으로 json 통신하기 때문에 거의 이렇게 쓴다고 보면된다.
          xhr.send(); // 서버에 요청을 보낸다.
    
          // 서버로 요청하면 그 결과가 응답이 왔을때 onload에 있는 코드가 실행된다.
          xhr.onload = () => {
            console.log(xhr);
            // 상태 코드 200, 성공
            if (xhr.status === 200) {
              const res = JSON.parse(xhr.response); // object로 받아 온다.
              console.log(res);
              return res;
            } else {
              console.log(xhr.status, xhr.statusText);
            }
          };
        }
    
        // 생성
        function postData() {
          const xhr = new XMLHttpRequest();
          xhr.open("POST", "http://localhost:3000/posts");
          xhr.setRequestHeader("content-type", "application/json;charset=UTF-8"); // charset=UTF-8 한글이 깨지는 것 방지한다.
          const data = { title: "xmlhttprequest", author: "John Doe" }; // json은 유일한 키값이 항상 존재해하기 때문에 id가 반드시 있어야 한다. id번호는 자동으로 생성된다.
          xhr.send(JSON.stringify(data)); // object를 문자열로 변환한다.
    
          xhr.onload = () => {
            if (xhr.status === 201) {
              // 상태 코드 201, 생성만 201, 조회, 수정, 삭제는 200번이다.
              const res = JSON.parse(xhr.response);
              console.log(res);
            } else {
              console.log(xhr.status, xhr.statusText);
            }
          };
        }
    
        // 수정
        function putData() {
          const xhr = new XMLHttpRequest();
          xhr.open("PUT", "http://localhost:3000/posts/2"); // id 2번 수정
          xhr.setRequestHeader("content-type", "application/json;charset=UTF-8");
          const data = { title: "javascript", author: "jeremy" };
          xhr.send(JSON.stringify(data));
    
          xhr.onload = () => {
            if (xhr.status === 200) {
              const res = JSON.parse(xhr.response);
              console.log(res);
            } else {
              console.log(xhr.status, xhr.statusText);
            }
          };
        }
    
        // 삭제
        function deleteData() {
          const xhr = new XMLHttpRequest();
          xhr.open("DELETE", "http://localhost:3000/posts/2"); // id 2번 삭제
          xhr.setRequestHeader("content-type", "application/json;charset=UTF-8");
          xhr.send();
    
          xhr.onload = () => {
            if (xhr.status === 200) {
              const res = JSON.parse(xhr.response);
              console.log(res);
            } else {
              console.log(xhr.status, xhr.statusText);
            }
          };
        }
      </script>
    </body>

     


     

    Fetch API

    Fetch API를 이용하면 XMLHttpRequest와 동일한 기능을 더 쉽게 구현할 수 있다. Fetch API는 그 자체가 Promise이다.

    <body>
      <button onclick="getData()">조회</button>
      <button onclick="postData()">생성</button>
      <button onclick="putData()">수정</button>
      <button onclick="deleteData()">삭제</button>
      <script>
        // 조회
        function getData() {
          fetch("http://localhost:3000/posts")
            // .then(function (response) {
            //   return response.json(); // json 으로 변환
            // })
            // .then(function (json) {
            //   console.log(json);
            // });
            // 위 조회 코드를 화살표 함수로 구현
            .then((response) => response.json()) // json 으로 변환
            .then((json) => console.log(json));
        }
    
        // 생성
        function postData() {
          fetch("http://localhost:3000/posts", {
            method: "POST",
            body: JSON.stringify({
              title: "xmllhttprequest",
              author: "John Doe",
            }),
            headers: {
              "content-type": "application/json;charset=UTF-8",
            },
          })
            .then((response) => response.json())
            .then((json) => console.log(json));
        }
    
        // 수정
        function putData() {
          // id 3번 수정
          fetch("http://localhost:3000/posts/3", {
            method: "PUT",
            body: JSON.stringify({
              title: "xmlhttprequest",
              author: "John Doe",
            }),
            headers: {
              "content-type": "application/json;charset=UTF-8",
            },
          })
            .then((response) => response.json())
            .then((json) => console.log(json));
        }
    
        // 삭제
        function deleteData() {
          // id 3번 삭제
          fetch("http://localhost:3000/posts/3", {
            method: "DELETE",
          })
            .then((response) => response.json())
            .then((json) => console.log(json));
        }
      </script>
    </body>

     


     

    Promise

    Promise는 비동기 처리에 사용되는 객체이다. 비동기란 언제 끝날지 모르는 작업을 기다리지 않고 다음 작업을 처리하는 것을 말한다.

    Promise 미적용

    <script>
      function getData() {
        const xhr = new XMLHttpRequest();
        xhr.open("GET", "http://localhost:3000/posts"); // 요청방식과 서버URL을 넣어준다. 준비 상태이다.
        xhr.setRequestHeader("content-type", "application/json"); // 요청에 맞는 헤더 값을 설정해야 한다. 서버와는 일반적으로 json 통신하기 때문에 거의 이렇게 쓴다고 보면된다.
        xhr.send(); // 서버에 요청을 보낸다.
    
        // 서버로 요청하면 그 결과가 응답이 왔을때 onload에 있는 코드가 실행된다.
        xhr.onload = () => {
          console.log(xhr);
          // 상태 코드 200, 성공
          if (xhr.status === 200) {
            const res = JSON.parse(xhr.response); // object로 받아 온다.
            return res;
          } else {
            console.log(xhr.status, xhr.statusText);
          }
        };
      }
    
      const data = getData();
      console.log(data); // undefined(시차 발생, 서버가 요청에 대한 응답을 보내주기 전에 실행되었다.)
      console.log("다음 코드 실행");
    </script>

     

    Promise 적용

    <script>
      // Promise 문법은 다음과 같다.
      // const promise = new Promise((resolve, reject) => {
      //   if ("처리성공") {
      //     resolve("결과 데이터");
      //   } else {
      //     reject("에러");
      //   }
      // });
      function getData2(url) {
        return new Promise((resolve, reject) => {
          const xhr = new XMLHttpRequest();
          xhr.open("GET", url);
          xhr.setRequestHeader("content-type", "application/json");
          xhr.send();
    
          xhr.onload = () => {
            if (xhr.status === 200) {
              const res = JSON.parse(xhr.response);
              resolve(res);
            } else {
              console.log(xhr.status, xhr.statusText);
              reject(xhr.status);
            }
          };
        });
      }
    
      // Promise를 적용하면 호출하는 쪽에서 then을 정의하여 사용할 수 있다.
      // 서버로부터 응답받기 전에 실행되어 버리면 문제가 되는 코드를 then 함수 내에 작성한다.
      getData2("http://localhost:3000/posts").then((res) => console.log(res)); // 2순위 실행
      getData2("http://localhost:3000/comments").then((res) => console.log(res)); // 3순위 실행
      console.log("다음 코드 실행"); // 1순위 실행
    
      // getData1().then(getData2().then(getData3())) // 참고로 Promise는 이렇게 연결하여 사용할 수 있지만 복잡해진다.
      // 아래는 위 코드와 같다.
      // const data1 = getData1();
      // const data2 = getData2(data1);
      // const data3 = getData3(data2);
    </script>

     

    Promise 없는 then은 error

    <script>
      function getCommentData(url) {
        const xhr = new XMLHttpRequest();
        xhr.open("GET", url);
        xhr.setRequestHeader("content-type", "application/json");
        xhr.send();
    
        xhr.onload = () => {
          if (xhr.status === 200) {
            const res = JSON.parse(xhr.response);
            return res;
          } else {
            console.log(xhr.status, xhr.statusText);
          }
        };
      }
    
      // promise를 적용하지 않았는데 호출하는 쪽에서 then을 사용하면 에러가 발생한다.
      getCommentData("http://localhost:3000/posts").then((res) =>
        console.log(res)
      ); // Uncaught TypeError: Cannot read properties of undefined (reading 'then')
    </script>

     


     

    Async/Await

    Async/Await는 Promise와 동일한 목적으로 사용한다. 비동기 통신이 완벽히 끝날 때까지 기다린다. 서버와 통신 중 순차적으로 코드를 실행할 때 주로 사용한다. 데이터가 너무 많아서 아래 코드가 마냥 기다리게 하는 상황이 발생할 수 있다. 따라서 서버로 부터 응답 받은 데이터를 다음 코드에서 반드시 필요로 할때 사용한다. 그런 상황이 아니라면 데이터를 덜 받았더라도 나머지 코드가 실행되도록 해야 한다. 상황에 따라 잘 사용하자!

    <body>
      <button onclick="getData()">조회</button>
      <script>
        // function 앞에 async를 붙인다.
        async function getData() {
          const res1 = await fetch("http://localhost:3000/posts"); // 서버로 부터 값이 오기를 기다렸다가 res1에 넣겠다.
          // const res1Json = res1.json(); // 콘솔창에 "Promise {<pending>}" 표시되었다. res1Json에 배열 요소가 들어오지 않은 상태이다.
          const res1Json = await res1.json(); // await으로 인해 res1Json에 배열 요소가 모두 들어온다.
          console.log(res1Json);
        }
      </script>
    </body>
    728x90
    반응형
    댓글