분류 javascript

제 9 장 비동기 JavaScript로 작업

컨텐츠 정보

  • 조회 215 (작성일 )

본문

REST API 및 XMLHttpRequest 


물어 보면 JavaScript가 정말 유용합니다. 브라우저에서 실행되는 스크립팅 언어로서 다음과 같은 모든 종류의 작업을 수행 할 수 있습니다.

  • 요소를 동적으로 생성
  • 대화형 기능 추가

등등. 8 장에서는 배열에서 시작하여 HTML 테이블을 작성했습니다. 하드 코딩 된 배열은 동기식 데이터 소스입니다. 즉, 코드에서 바로 사용할 수 있습니다. 기다릴 필요가 없습니다. 그러나 JavaScript 개발자는 대부분 네트워크에서 외부에서 데이터를 가져 오려고 합니다.

네트워크 통신은 동기식 데이터 소스와 달리 항상 비동기식 작업입니다. 데이터를 요청하고 서버가 나중에 "응답"할 수 있습니다. JavaScript에는 자체적으로 비동기성이 내장되어 있지 않습니다. 시간이 많이 걸리는 작업을 처리하기 위해 외부 도우미를 제공하는 "호스트" 환경 (브라우저 또는 Node.js)입니다.

3 장에서는 시간 기반 작업을 위한 두 개의 브라우저 API인 setTimeout 및 setInterval을 보았습니다. 브라우저는 많은 API를 제공하며 특히 네트워크 요청을 목표로 하는 XMLHttpRequest라는 API가 있습니다. 이름이 정말 이상합니다. XMLHttpRequest는 '00'처럼 들립니다.

실제로 대부분의 웹 서비스가 XML 형식으로 데이터를 노출한 시대에서 비롯되었습니다. 요즘 JSON은 웹 서비스간에 데이터를 이동하는 데 가장 많이 사용되는 통신 "프로토콜"이지만 XMLHttpRequest라는 이름은 결국 멈췄습니다.

XMLHttpRequest는 또한 이름이 "Asynchronous JavaScript and XML"을 나타내는 AJAX라는 일련의 기술의 일부입니다.

AJAX는 브라우저에서 가능한 한 매끄러운 네트워크 요청을 하기 위해 탄생했습니다. 즉, 페이지를 새로 고치지 않고도 원격 소스에서 데이터를 가져올 수 있습니다. 당시 아이디어는 거의 혁명적이었습니다.

XMLHttpRequest (거의 13년 전)가 도입되면서 이 플랫폼은 네트워크 요청을 위한 기본 방법을 얻었습니다. 하나 만드는 방법은 다음과 같습니다.


https://github.com/valentinogagliardi/Little-JavaScript-Book/blob/v1.0.0/manuscript/chapter9.md 


var request = new XMLHttpRequest();

request.open("GET", "https://academy.valentinog.com/api/link/");

request.addEventListener("load", function() {
  console.log(this.response);
});

request.send();


이 예에서 우리는 :

  • 새로운 XMLHttpRequest 객체를 생성
  • 메소드와 URL을 제공하여 요청을 여십시오.
  • 이벤트 리스너 등록
  • 요청을 보내기

XMLHttpRequest는 DOM 이벤트를 기반으로 하며 (새로 고침에 대해서는 8 장 참조) 요청이 성공할 때마다 트리거 되는 "로드"이벤트를 수신하기 위해 addEventListener 또는 onload를 사용할 수 있습니다. 실패한 요청 (네트워크 오류)의 경우 "error"이벤트에 리스너를 등록 할 수 있습니다.


var request = new XMLHttpRequest();

request.open("GET", "https://academy.valentinog.com/api/link/");

request.onload = function() {
  console.log(this.response);
};

request.onerror = function() {
  // handle the error
};

request.send();


이 지식으로 무장한 XMLHttpRequest를 잘 활용하자.


XMLHttpRequest 작동 : HTML 및 JavaScript로 목록 생성 


REST API에서 데이터를 가져온 후 간단한 HTML 목록을 작성합니다. 선택한 폴더에 build-list.html이라는 새 파일을 만듭니다.


<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <title>XMLHttpRequest</title>
</head>
<body>

</body>
<script src="xhr.js"></script>
</html>


다음으로 같은 폴더에 xhr.js라는 파일을 만듭니다. 이 파일에서는 새로운 XHR 요청을 작성합니다 (이제부터 ECMAScript 2015를 작성합니다).


"use strict";

const request = new XMLHttpRequest();


위의 호출 (생성자 호출, 5 장 확인)은 XMLHttpRequest 유형의 새 객체를 만듭니다. 콜백을 인수로 예상하는 setTimeout과 같은 비동기 함수와 반대


setTimeout(callback, 10000);

function callback() {
  console.log("hello timer!");
}


XMLHttpRequest는 DOM 이벤트를 기반으로 하며 핸들러 콜백은 onload 객체에 등록됩니다. 요청이 성공하면 로드 이벤트가 발생합니다. 같은 파일에서 나와 함께 코딩하십시오.

"use strict";

const request = new XMLHttpRequest();

request.onload = callback;

function callback() {
  console.log("Got the response!");
}


콜백을 등록한 후 open()으로 요청을 열 수 있습니다. HTTP 메소드 (이 경우 GET)와 전화 URL을 허용합니다.


"use strict";

const request = new XMLHttpRequest();

request.onload = callback;

function callback() {
  console.log("Got the response!");
}

request.open("GET", "https://academy.valentinog.com/api/link/");


마지막으로 send()로 실제 요청을 보낼 수 있습니다.


"use strict";

const request = new XMLHttpRequest();

request.onload = callback;

function callback() {
  console.log("Got the response!");
}

request.open("GET", "https://academy.valentinog.com/api/link/");
request.send();


브라우저에서 build-list.html을 열면 "응답을 받았습니다!"가 표시됩니다 콘솔에서. 어쨌든 문자열을 인쇄하는 것은 거의 쓸모가 없습니다. 우리는 서버의 응답에 관심이 있습니다. 6 장을 기억한다면 모든 일반 JavaScript 함수는 호스트 객체에 대한 참조를 보유합니다. 콜백이 XMLHttpRequest 객체 내에서 실행 중이므로 this.response에서 서버 응답에 액세스 할 수 있습니다.


"use strict";

const request = new XMLHttpRequest();

request.onload = callback;

function callback() {
  // this refers to the new XMLHttpRequest
  // response is the server's response
  console.log(this.response);
}

request.open("GET", "https://academy.valentinog.com/api/link/");
request.send();


파일을 저장하고 build-list.html을 새로 고치십시오. 콘솔에서 서버의 응답이 텍스트 문서로 표시되어야 합니다. 텍스트 표현이 아닌 JSON 문서에 관심이 있기 때문에 여전히 유용하지 않습니다. 이 시점에서 두 가지 옵션이 있습니다.

  • 옵션 1 : XMLHttpRequest 객체에서 응답 유형 구성
  • 옵션 2 : JSON.parse() 사용

첫 번째 옵션은 더 해킹적입니다. 줄이 매달려 있는 것을 좋아하지 않습니다.


"use strict";

const request = new XMLHttpRequest();

request.onload = callback;

function callback() {
  // this refers to the new XMLHttpRequest
  // response is the server's response
  console.log(this.response);
}

// configure the response type
request.responseType = "json";
//

request.open("GET", "https://academy.valentinog.com/api/link/");
request.send();


대신 옵션 2는 보다 설명적이고 관용적입니다.


"use strict";

const request = new XMLHttpRequest();

request.onload = callback;

function callback() {
  const response = JSON.parse(this.response);
  console.log(response);
}

request.open("GET", "https://academy.valentinog.com/api/link/");
request.send();


build-list.html을 다시 새로 고치면 각각 모양이 동일한 JavaScript 객체 배열이 표시됩니다.


[
  //
  {
    title:
      "JavaScript Engines: From Call Stack to Promise, (almost) Everything You Need to Know",
    url: "https://www.valentinog.com/blog/engines/",
    tags: ["javascript", "v8"],
    id: 3
  }
  //
]


우리는 8장에서 처럼 수동으로 배열을 만들지 않았습니다. 이번에는 REST API에서 나왔습니다. 이 배열을 사용하여 HTML 페이지를 요소 목록으로 채 웁니다. 이 과정에서 JavaScript 클래스에 대한 새로운 기능도 볼 수 있습니다.


JavaScript를 사용하여 HTML 목록 작성 (및 디버깅 클래스) 


JavaScript를 구성하는 방법에는 여러 가지가 있습니다. 4 장에서는 변수와 함수에 프라이버시를 제공하는 수단으로 모듈을 살펴 보았습니다. 5 장에서 우리는 JavaScript 코드를 배열하기 위한 사실상의 "표준"인 프로토 타입을 살펴 보았습니다. ECMAScript 2015 클래스도 프로토 타입 기반 코드를 작성하는 더 멋진 방법이며, 솔직히 말해서 최근 몇 년 동안 많은 관심을 끌고 있는 스타일을 보았습니다. 여기서는 HTML 목록 작성기를 JavaScript 클래스로 캡슐화하여 실습 할 수 있습니다.

개인 클래스 필드도 사용합니다 (이 글을 쓰는 시점에는 Firefox에서 지원되지 않음). 코드를 작성하기 전에 "클래스가 어떻게 사용됩니까?"라고 생각하고 싶습니까? 예를 들어 다른 개발자가 다음 코드를 전달하여 코드를 가져 와서 클래스를 호출 할 수 있습니다.

  • 데이터를 가져 오기 위한 원격 URL
  • 리스트를 첨부하는 HTML 요소

아이디어는 다음과 같습니다.


const url = "https://academy.valentinog.com/api/link/";
const target = document.body;
const list = new List(url, target);


이러한 요구 사항을 염두에 두고 클래스 코딩을 시작할 수 있습니다. 지금은 생성자에 몇 개의 인수를 허용하고 데이터를 가져 오는 더미 메소드가 있어야 합니다.


class List {
  constructor(url, target) {
    this.url = url;
    this.target = target;
  }

  getData() {
    return "stuff";
  }
}


소프트웨어 개발의 일반적인 지혜는 반원과 방법을 반대해야 할 이유가 없다면 (코드 완성 2, 좋은 캡슐화) 외부에서 접근 할 수 없다는 것입니다. JavaScript에는 모듈을 사용하지 않는 한 메소드와 변수를 숨기는 기본 방법이 없습니다 (2 장). 클래스조차도 정보 유출에 영향을 받지 않지만 개인 클래스 분야에서는 최종적으로 개인 정보 보호를 받을 수 있습니다. 어떤 비용으로 요청할 수 있습니까? 개인 클래스 필드의 구문은 드문 경우입니다. Bash 및 Python 주석과 동일하다고 말하면 어떻게 됩니까? class을 다시 작성하는 방법은 다음과 같습니다.


class List {
  #url;
  #target;

  constructor(url, target) {
    this.#url = url;
    this.#target = target;
  }

  getData() {
    return "stuff";
  }
}


구문이 마음에 들지 않지만 개인 클래스 필드는 해당 작업을 수행합니다. 외부에서 URL이나 대상에 액세스 할 수 없습니다.


class List {
  #url;
  #target;

  constructor(url, target) {
    this.#url = url;
    this.#target = target;
  }

  getData() {
    return "stuff";
  }
}

const url = "https://academy.valentinog.com/api/link/";
const target = document.body;
const list = new List(url, target);

console.log(list.url); // undefined
console.log(list.target); // undefined


정상적인 기본값입니다. 그리고 구조가 정해지면 getData 내에서 데이터 페치 로직을 이동할 수 있습니다. 이번에는 request.open의 두 번째 인수는 다음과 같아야 합니다.


"use strict";

class List {
  #url;
  #target;

  constructor(url, target) {
    this.#url = url;
    this.#target = target;
  }

  getData() {
    const request = new XMLHttpRequest();
    request.onload = function() {
      const response = JSON.parse(this.response);
      console.log(response);
    };

    request.open("GET", this.#url);
    request.send();
  }
}

const url = "https://academy.valentinog.com/api/link/";
const target = document.body;
const list = new List(url, target);


잘 오고 있어요! 이제 링크를 표시하기 위해 getData 바로 뒤에 render라는 메소드를 추가하겠습니다. render는 매개 변수로 전달 된 배열에서 시작하여 HTML 목록을 만듭니다.


"use strict";

class List {
  #url;
  #target;

  constructor(url, target) {
    this.#url = url;
    this.#target = target;
  }

  getData() {
    const request = new XMLHttpRequest();
    request.onload = function() {
      const response = JSON.parse(this.response);
      console.log(response);
    };

    request.open("GET", this.#url);
    request.send();
  }

  // The new method
  render(data) {
    const ul = document.createElement("ul");
    for (const element of data) {
      const li = document.createElement("li");
      const title = document.createTextNode(element.title);
      li.appendChild(title);
      ul.appendChild(li);
    }
    this.#target.appendChild(ul);
  }
}


document.createElement(), document.createTextNode() 및 appendChild()를 확인하십시오. DOM 조작에 관해 이야기 할 때 8 장에서 그것들을 보았습니다. 개인 필드 this. # target은 여기서 HTML 목록을 DOM에 추가하는 데 사용됩니다. 이제 물건을 조금 더 밀어 내고 싶습니다.

  • JSON 응답을 얻은 후 호출 렌더링
  • 사용자가 새로운 List "인스턴스"를 생성하자마자 getData 호출

이를 위해 request.onload 콜백 내부에서 render를 호출합니다.


getData() {
    const request = new XMLHttpRequest();
    request.onload = function() {
      const response = JSON.parse(this.response);
      // Call render after getting the response
      this.render(response);
    };

    request.open("GET", this.#url);
    request.send();
  }


반면에 getData는 생성자 내에서 실행되어야 합니다.


constructor(url, target) {
    this.#url = url;
    this.#target = target;
    // Call getData as soon as the class is used
    this.getData();
  }


완전한 코드는 다음과 같습니다.


"use strict";

class List {
  #url;
  #target;

  constructor(url, target) {
    this.#url = url;
    this.#target = target;
    this.getData();
  }

  getData() {
    const request = new XMLHttpRequest();
    request.onload = function() {
      const response = JSON.parse(this.response);
      this.render(response);
    };

    request.open("GET", this.#url);
    request.send();
  }

  render(data) {
    const ul = document.createElement("ul");
    for (const element of data) {
      const li = document.createElement("li");
      const title = document.createTextNode(element.title);
      li.appendChild(title);
      ul.appendChild(li);
    }
    this.#target.appendChild(ul);
  }
}

const url = "https://academy.valentinog.com/api/link/";
const target = document.body;
const list = new List(url, target);


브라우저에서 build-list.html을 새로 고치고 콘솔을 살펴보십시오.


Uncaught TypeError: this.render is not a function


this.render는 함수가 아닙니다! 뭐가 될 수 있었는지? 이 시점에서 6 장 이상으로 접근하여 코드를 디버깅 할 수 있습니다. getData에서 this.render (response) 바로 다음에 명령 디버거를 추가하십시오.


getData() {
    const request = new XMLHttpRequest();
    request.onload = function() {
      const response = JSON.parse(this.response);
      debugger;
      this.render(response);
    };

    request.open("GET", this.#url);
    request.send();
  }


디버거는 소위 중단 점을 추가하고 실행이 중지됩니다. 이제 브라우저 콘솔을 열고 build-list.html을 새로 고칩니다. 다음은 Chrome에서 표시되는 내용입니다.


PICTURE 


"범위"탭을 자세히 살펴보십시오. getData 내부에는 실제로 이것이 있지만 XMLHttpRequest를 가리킵니다. 다른 말로, 우리는 잘못된 객체에서 this.render에 액세스 하려고 합니다. 우리는 this.render를 "새 List 객체에 대한 렌더링 메소드"로 정의했습니다 (클래스는 객체 생성을 위한 팩토리 함수일 뿐입니다). 이것이 왜 일치하지 않습니까? request.onload에 전달 된 콜백은 const request = new XMLHttpRequest() 호출의 결과 인 XMLHttpRequest 유형의 호스트 객체에서 실행되기 때문입니다.

이 시점에서 길을 잃었습니까? 아직. 화살표 함수를 기억하십니까? 6 장에서 우리는 그들이 이런 상황에 도움이 될 수 있음을 보았습니다. 일반 함수 대신 화살표 함수를 request.onload의 콜백으로 사용하면 어떻게 됩니까? 해보자:


getData() {
    const request = new XMLHttpRequest();
    // The arrow function in action
    request.onload = () => {
      const response = JSON.parse(this.response);
      debugger;
      this.render(response);
    };

    request.open("GET", this.#url);
    request.send();
  }


build-list.html을 새로 고치고 확인하십시오.


Uncaught SyntaxError: Unexpected token u in JSON at position 0


그럴 수 있지. 이전 오류는 사라졌지 만 이제 JSON.parse에 문제가 있습니다. 우리는 this가 이와 관련이 있다고 쉽게 상상할 수 있습니다. 디버거를 한 줄 위로 이동하십시오.


getData() {
    const request = new XMLHttpRequest();
    request.onload = () => {
      debugger;
      const response = JSON.parse(this.response);
      this.render(response);
    };

    request.open("GET", this.#url);
    request.send();
  }


build-list.html을 새로 고치고 브라우저 콘솔에서 범위를 다시 보십시오. 이제 우리가 액세스하려는 것은 List이기 때문에 응답이 정의되지 않았습니다. 화살표 함수와 클래스의 동작과 일관됩니다 (클래스는 기본적으로 엄격 모드에 있습니다). 이제 수정이 무엇입니까? 8 장, DOM 및 이벤트에서 이벤트 리스너로 전달 된 모든 콜백은 이벤트 객체에 액세스 할 수 있음을 알아야 합니다. 해당 이벤트 객체 내부에는 target이라는 속성이 있으며 이벤트를 시작한 객체를 가리킵니다. 빙고! event.target.response에서 응답에 액세스 할 수 있습니다.


getData() {
    const request = new XMLHttpRequest();
    request.onload = event => {
      const response = JSON.parse(event.target.response);
      this.render(response);
    };

    request.open("GET", this.#url);
    request.send();
  }


완전한 코드는 다음과 같습니다.


"use strict";

class List {
  #url;
  #target;

  constructor(url, target) {
    this.#url = url;
    this.#target = target;
    this.getData();
  }

  getData() {
    const request = new XMLHttpRequest();
    request.onload = event => {
      const response = JSON.parse(event.target.response);
      this.render(response);
    };

    request.open("GET", this.#url);
    request.send();
  }

  render(data) {
    const ul = document.createElement("ul");
    for (const element of data) {
      const li = document.createElement("li");
      const title = document.createTextNode(element.title);
      li.appendChild(title);
      ul.appendChild(li);
    }
    this.#target.appendChild(ul);
  }
}

const url = "https://academy.valentinog.com/api/link/";
const target = document.body;
const list = new List(url, target);


build-list.html을 다시 새로 고치면 모든 것이 예상대로 작동합니다! 페이지에 링크 목록이 나타납니다! 성가신 버그를 잡은 후에는 디버거를 제거하십시오. 또는 브라우저 콘솔에서 직접 소스 코드에 중단 점을 배치 할 수 있습니다. 이제 XMLHttpRequest : Fetch의 진화에 대해 알아 보겠습니다.


비동기식 진화 : XMLHttpRequest에서 Fetch로 


Axios와 같은 라이브러리를 선호하는 경우가 종종 있지만 Fetch API는 AJAX 요청을 작성하는 기본 브라우저 방법입니다. Fetch는 ECMAScript 2015 및 새로운 Promise 객체와 함께 2015 년에 탄생했습니다. 반면 AJAX는 1999 년부터 브라우저에서 데이터를 가져 오는 기술 세트가 존재했습니다.

요즘 우리는 AJAX와 Fetch를 당연한 것으로 생각하지만 Fetch가 XMLHttpRequest의 "예찬화 된 버전"에 불과하다는 것을 아는 사람은 거의 없습니다. 가져 오기는 일반적인 XMLHttpRequest 요청보다 더 간결하며 더 중요한 것은 Promise을 기반으로 합니다. 간단한 요청은 다음과 같습니다.


fetch("https://academy.valentinog.com/api/link/").then(function(response) {
  console.log(response);
});


브라우저에서 실행하면 콘솔이 응답 개체를 인쇄합니다. 요청되는 콘텐츠 유형에 따라 데이터를 반환하는 동안 콘텐츠를 JSON으로 변환해야 합니다.


fetch("https://academy.valentinog.com/api/link/").then(function(response) {
  return response.json();
});


콜만으로는 실제 데이터를 반환하지 않는다고 생각하는 것과 반대로. response.json()도 Promise를 반환하므로 JSON 페이 로드를 가져 오려면 한 가지 단계가 더 필요합니다.


fetch("https://academy.valentinog.com/api/link/")
  .then(function(response) {
    return response.json();
  })
  .then(function(json) {
    console.log(json);
  });


Fetch는 XMLHttpRequest보다 훨씬 편리하고 깨끗하지만 많은 특성이 있습니다. 예를 들어 오류에 대한 응답을 확인할 때 특히 주의해야 합니다. 다음 섹션에서는 Fetch를 처음부터 다시 작성하는 동안 이에 대해 자세히 알아 봅니다.


처음부터 Fetch API 재 구축 (재미와 수익을 위해) 


대부분의 경우 자바 스크립트 개발자는 라이브러리 작성자가 만든 Promise을 "소비"합니다. 그것은 대부분의 개발자들이 Promise을 알고 다른 사람들의 코드 소비자로 사용했기 때문에 Promise에 대해 알고 있음을 의미합니다. 그러나 그것은 이야기의 절반에 지나지 않습니다. 반면 라이브러리 제작자는 기존 코드를 래핑하고 개발자에게 보다 현대적인 API를 제공하기 위해 Promise를 사용합니다. 이것이 바로 우리가 할 일입니다. Fetch를 위한 소위 polyfill을 만들 것입니다. fetch.html이라는 새 파일을 만들고 선택한 폴더에 저장하십시오.


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Building Fetch from scratch</title>
</head>
<body>

</body>
<script src="fetch.js"></script>
</html>


그런 다음 동일한 폴더에 fetch.js라는 다른 파일을 만들고 다음 코드로 시작하십시오.


"use strict";

window.fetch = null;


첫 번째 줄에서는 엄격 모드에 있고 두 번째 줄에서는 원래 Fetch API를 "무효화"합니다. 이제 자체 Fetch API 구축을 시작할 수 있습니다. 그러나 먼저 실제 Fetch를 리버스 엔지니어링하겠습니다. Fetch의 작동 방식은 매우 간단합니다. URL을 가져 와서 GET 요청을 합니다.


fetch("https://academy.valentinog.com/api/link/").then(function(response) {
  console.log(response);
});


함수가 "체인 가능"인 경우 Promise을 반환함을 의미합니다. fetch.js 안에 fetch라는 함수를 만들어 보자.이 함수는 URL을 가져 와서 새로운 Promise을 돌려 준다 Promise를 만들려면 Promise 생성자를 호출하고 확인 및 거부를 수행하는 콜백을 전달할 수 있습니다.


function fetch(url) {
  return new Promise(function(resolve, reject) {
    // do stuff
  });
}


연기 테스트를 해봅시다 (작동해야 함).


function fetch(url) {
  return new Promise(function(resolve, reject) {
    resolve("Fake response!");
  });
}


이제 완전한 코드를 시도하십시오.


"use strict";

window.fetch = fetch;

function fetch(url) {
  return new Promise(function(resolve, reject) {
    resolve("Fake response!");
  });
}

fetch("https://academy.valentinog.com/api/link/").then(function(response) {
  console.log(response);
});


"가짜 응답!" 콘솔 내부. 효과가 있다! 물론 API에서 아무것도 반환 되지 않기 때문에 여전히 쓸모없는 Fetch입니다. XMLHttpRequest의 도움을 받아 실제 동작을 구현해 봅시다. 우리는 이미 XMLHttpRequest로 요청을 생성하는 방법을 보았다 :


const request = new XMLHttpRequest();
request.open("GET", "https://academy.valentinog.com/api/link/");
request.send();
request.onload = function() {
  console.log(this.response);
};


호환성을 극대화하기 위해 레거시 객체 onload를 사용합니다. 우리가 지금 할 일은 XMLHttpRequest를 Promise로 감싸는 것입니다. fetch.js를 열고 fetch 함수를 확장하여 새 XMLHttpRequest를 포함하십시오.


function fetch(url) {
  return new Promise(function(resolve, reject) {
    const request = new XMLHttpRequest();
    request.open("GET", url);
    request.onload = function() {
      // handle the response
    };
    request.send();
  });
}


이제 상황이 이해되기 시작합니까? 객체 onerror에 대한 실패 처리기를 등록 할 수도 있습니다.


function fetch(url) {
  return new Promise(function(resolve, reject) {
    const request = new XMLHttpRequest();
    request.open("GET", url);
    request.onload = function() {
      // handle the response
    };
    request.onerror = function() {
      // handle the error
    };
    request.send();
  });
}


이 시점에서 그것은 요청의 운명에 따라 약속을 해결하거나 거부하는 문제입니다. 이는 서버에서 받은 응답으로 onload 내부의 Promise을 해결하는 것을 의미합니다.


function fetch(url) {
  return new Promise(function(resolve, reject) {
    const request = new XMLHttpRequest();
    request.open("GET", url);
    request.onload = function() {
      resolve(this.response);
    };
    request.onerror = function() {
      // handle the error
    };
    request.send();
  });
}


오류가 발생하면 onerror 내의 Promise을 거부합니다.


function fetch(url) {
  return new Promise(function(resolve, reject) {
    const request = new XMLHttpRequest();
    request.open("GET", url);
    request.onload = function() {
      resolve(this.response);
    };
    request.onerror = function() {
      reject("Network error!");
    };
    request.send();
  });
}


거부 된 Promise은 catch 처리기에서 처리하므로 "가짜" Fetch를 다음과 같이 사용할 수 있습니다.


fetch("https://acdemy.valentinog.com/api/link/")
  .then(function(response) {
    console.log(response);
  })
  .catch(function(error) {
    console.log(error);
  });


이제 잘못된 URL의 경우 Fetch에 "Network error!"가 표시됩니다. 콘솔에. URL이 올바른 경우 서버에서 응답을 얻습니다.


The textual response from the server 


환상적인! Fetch polyfill은 작동하지만 가장자리가 거칠습니다. 우선 JSON을 반환하는 함수를 구현해야 합니다. 실제 Fetch API는 나중에 JSON, blob 또는 text로 변환 할 수 있는 응답을 생성합니다 (이 연습의 범위에서는 JSON 함수 만 구현 함).


fetch("https://academy.valentinog.com/api/link/")
  .then(function(response) {
    return response.json();
  })
  .then(function(json) {
    console.log(json);
  })


기능을 쉽게 구현할 수 있어야 합니다. "응답"자체에 json() 함수가 첨부 된 엔티티일 수 있습니다. JavaScript 프로토 타입 시스템은 코드 구조화에 적합한 후보입니다 (5 장 참조). Response라는 생성자 함수와 프로토 타입 (fetch.js)에 바인딩 된 메소드를 작성해 보겠습니다.


function Response(response) {
  this.response = response;
}

Response.prototype.json = function() {
  return JSON.parse(this.response);
};


그게 다야. 이제 Fetch 내에서 Response를 사용할 수 있습니다.


function fetch(url) {
  return new Promise(function(resolve, reject) {
    const request = new XMLHttpRequest();
    request.open("GET", url);
    request.onload = function() {
      // Before:
      // resolve(this.response);
      // After:
      const response = new Response(this.response);
      resolve(response);
    };
    request.onerror = function() {
      reject("Network error!");
    };
    request.send();
  });
}


지금까지 완전한 코드는 다음과 같습니다.


window.fetch = fetch;

function Response(response) {
  this.response = response;
}

Response.prototype.json = function() {
  return JSON.parse(this.response);
};

function fetch(url) {
  return new Promise(function(resolve, reject) {
    const request = new XMLHttpRequest();
    request.open("GET", url);
    request.onload = function() {
      // Before:
      // resolve(this.response);
      // After:
      const response = new Response(this.response);
      resolve(response);
    };
    request.onerror = function() {
      reject("Network error!");
    };
    request.send();
  });
}

fetch("https://academy.valentinog.com/api/link/")
  .then(function(response) {
    return response.json();
  })
  .then(function(json) {
    console.log(json);
  })
  .catch(function(error) {
    console.log(error);
  });


위의 코드는 객체의 배열을 브라우저의 콘솔에 인쇄해야 합니다. 이제 오류를 처리하겠습니다. Fetch의 실제 버전은 폴리 필보다 훨씬 복잡하지만 동일한 정확한 동작을 복제하는 것은 그리 어렵지 않습니다. Fetch의 Response 객체에는 "ok"라는 부울 속성이 있습니다. 해당 부울은 요청이 성공하면 true로 설정되고 요청이 실패하면 false로 설정됩니다. 개발자는 오류를 발생시켜 부울을 확인하고 약속 핸들러를 대체 할 수 있습니다. 실제 페치로 상태를 확인하는 방법은 다음과 같습니다.


fetch("https://academy.valentinog.com/api/link/")
  .then(function(response) {
    if (!response.ok) {
      throw Error(response.statusText);
    }
    return response.json();
  })
  .then(function(json) {
    console.log(json);
  })
  .catch(function(error) {
    console.log(error);
  });


보시다시피 "statusText"도 있습니다. Response 객체에서 "ok"와 "statusText"를 모두 쉽게 구현할 수 있습니다. 서버가 응답 할 때 특히 response.ok가 true 여야 합니다.


  • 상태 코드가 200 이하
  • 상태 코드가 300보다 작은

fetch.js를 열고 Response 객체를 조정하여 새 속성을 포함 시킵니다. 또한 response.response에서 응답을 읽으려면 첫 번째 행을 변경하십시오.


function Response(response) {
  this.response = response.response;
  this.ok = response.status >= 200 && response.status < 300;
  this.statusText = response.statusText;
}


"statusText"를 구성 할 필요가 없는 것은 이미 원래 XMLHttpRequest 응답 객체에서 반환하기 때문입니다. 즉, 사용자 정의 응답을 구성 할 때 전체 응답만 전달하면 됩니다.


function fetch(url) {
  return new Promise(function(resolve, reject) {
    const request = new XMLHttpRequest();
    request.open("GET", url);
    request.onload = function() {
      // Before:
      // var response = new Response(this.response);
      // After: pass the entire response
      const response = new Response(this);
      resolve(response);
    };
    request.onerror = function() {
      reject("Network error!");
    };
    request.send();
  });
}


전체 코드는 다음과 같습니다.


"use strict";

window.fetch = fetch;

function Response(response) {
  this.response = response.response;
  this.ok = response.status >= 200 && response.status < 300;
  this.statusText = response.statusText;
}

Response.prototype.json = function() {
  return JSON.parse(this.response);
};

function fetch(url) {
  return new Promise(function(resolve, reject) {
    const request = new XMLHttpRequest();
    request.open("GET", url);
    request.onload = function() {
      // Before:
      // var response = new Response(this.response);
      // After: pass the entire response
      const response = new Response(this);
      resolve(response);
    };
    request.onerror = function() {
      reject("Network error!");
    };
    request.send();
  });
}

fetch("https://academy.valentinog.com/api/link/")
  .then(function(response) {
    if (!response.ok) {
      throw Error(response.statusText);
    }
    return response.json();
  })
  .then(function(json) {
    console.log(json);
  })
  .catch(function(error) {
    console.log(error);
  });


현재 Fetch 폴리 필은 오류와 성공을 처리 할 수 ​​있습니다. 다음과 같이 실패한 URL을 제공하여 오류 처리를 확인할 수 있습니다.


fetch("https://httpstat.us/400")
  .then(function(response) {
    if (!response.ok) {
      throw Error(response.statusText);
    }
    return response.json();
  })
  .then(function(json) {
    console.log(json);
  })
  .catch(function(error) {
    console.log(error);
  });


브라우저 콘솔에 "오류 : 잘못된 요청"이 표시됩니다. 잘 했어! 이제 POST 요청을 위해 폴리필을 확장하겠습니다. 먼저 실제 Fetch를 사용하여 POST 요청을 하는 방법을 살펴 보겠습니다. 우리가 서버에서 만들 엔티티는 제목과 URL이 있는 "링크"입니다.


const link = {
  title: "Building a Fetch Polyfill From Scratch",
  url: "https://www.valentinog.com/fetch-api/"
};


다음으로 Fetch 요청을 구성합니다. 적어도 HTTP 메소드, 요청의 컨텐츠 유형 (이 경우 JSON) 및 요청 본문 (새 링크)을 포함 해야 하는 객체에서 수행됩니다.


const link = {
  title: "Building a Fetch Polyfill From Scratch",
  url: "https://www.valentinog.com/fetch-api/"
};

const requestInit = {
  method: "POST",
  headers: {
    "Content-Type": "application/json"
  },
  body: JSON.stringify(link)
};


마지막으로 다음과 같이 요청합니다.


const link = {
  title: "Building a Fetch Polyfill From Scratch",
  url: "https://www.valentinog.com/fetch-api/"
};

const requestInit = {
  method: "POST",
  headers: {
    "Content-Type": "application/json"
  },
  body: JSON.stringify(link)
};

fetch("https://academy.valentinog.com/api/link/create/", requestInit)
  .then(function(response) {
    if (!response.ok) {
      throw Error(response.statusText);
    }
    return response.json();
  })
  .then(function(json) {
    console.log(json);
  })
  .catch(function(error) {
    console.log(error);
  });


그러나 이제 우리는 polyfill에 문제가 있습니다. 단일 매개 변수 "url"을 허용하고 이에 대한 GET 요청 만 작성합니다.


function fetch(url) {
  return new Promise(function(resolve, reject) {
    const request = new XMLHttpRequest();
    request.open("GET", url);
    request.onload = function() {
      const response = new Response(this);
      resolve(response);
    };
    request.onerror = function() {
      reject("Network error!");
    };
    request.send();
  });
}


문제를 해결하는 것은 쉬워야 합니다. 먼저 requestInit라는 두 번째 매개 변수를 사용할 수 있습니다. 그런 다음 해당 매개 변수를 기반으로 다음과 같은 새로운 Request 객체를 구성 할 수 있습니다.

  • 기본적으로 GET 요청을 합니다
  • 제공된 경우 requestInit의 본문, 메소드 및 헤더를 사용합니다.

먼저 body, method 및 header라는 일부 속성을 사용하여 새 Request 함수를 만듭니다. fetch.js를 열고 코딩하십시오 :


function Request(requestInit) {
  this.method = requestInit.method || "GET";
  this.body = requestInit.body;
  this.headers = requestInit.headers;
}


다음으로 우리는 polyfill 안에 새로운 기능을 사용할 것입니다. 요청을 열기 직전에 요청 구성 개체를 준비합니다.


function fetch(url, requestInit) {
  return new Promise(function(resolve, reject) {
    const request = new XMLHttpRequest();
    const requestConfiguration = new Request(requestInit || {});
    // more soon
  });
}


이제 요청은 Request 객체에 정의 된 메소드를 사용합니다.


function fetch(url, requestInit) {
  return new Promise(function(resolve, reject) {
    const request = new XMLHttpRequest();
    const requestConfiguration = new Request(requestInit || {});
    request.open(requestConfiguration.method, url);
    // more soon
  });
}


오류 및 성공 (로드) 처리 논리는 동일하게 유지됩니다.


function fetch(url, requestInit) {
  return new Promise(function(resolve, reject) {
    const request = new XMLHttpRequest();
    const requestConfiguration = new Request(requestInit || {});
    request.open(requestConfiguration.method, url);
    request.onload = function() {
      const response = new Response(this);
      resolve(response);
    };
    request.onerror = function() {
      reject("Network error!");
    };
    // more soon
  });
}


그러나 그 외에도 요청 헤더를 설정하기 위한 최소한의 논리를 추가 할 수 있습니다.


function fetch(url, requestInit) {
  return new Promise(function(resolve, reject) {
    const request = new XMLHttpRequest();
    const requestConfiguration = new Request(requestInit || {});
    request.open(requestConfiguration.method, url);
    request.onload = function() {
      const response = new Response(this);
      resolve(response);
    };
    request.onerror = function() {
      reject("Network error!");
    };
    // Set headers on the request
    for (const header in requestConfiguration.headers) {
      request.setRequestHeader(header, requestConfiguration.headers[header]);
    }
    // more soon
  });
}


setRequestHeader는 XMLHttpRequest 객체에서 즉시 사용할 수 있습니다. 헤더는 AJAX 요청을 구성하는 데 중요합니다. 대부분의 경우 헤더에서 application/json 또는 인증 토큰을 설정하려고 할 수 있습니다. 맨 위에 체리로서 다음과 같은 코드를 작성하여 보낼 수 있습니다.

  • 본문이 없는 경우 빈 요청
  • 페이 로드가 있는 요청은 본문이 제공됩니다.
function fetch(url, requestInit) {
  return new Promise(function(resolve, reject) {
    const request = new XMLHttpRequest();
    const requestConfiguration = new Request(requestInit || {});
    request.open(requestConfiguration.method, url);
    request.onload = function() {
      const response = new Response(this);
      resolve(response);
    };
    request.onerror = function() {
      reject("Network error!");
    };
    // Set headers on the request
    for (const header in requestConfiguration.headers) {
      request.setRequestHeader(header, requestConfiguration.headers[header]);
    }
    // If there's a body send it
    // If not send an empty GET request
    requestConfiguration.body && request.send(requestConfiguration.body);
    requestConfiguration.body || request.send();
  });
}


POST 요청 기능을 다루어야 합니다. 지금까지 Fetch 폴리필에 대한 전체 코드는 다음과 같습니다.


"use strict";

window.fetch = fetch;

function Response(response) {
  this.response = response.response;
  this.ok = response.status >= 200 && response.status < 300;
  this.statusText = response.statusText;
}

Response.prototype.json = function() {
  return JSON.parse(this.response);
};

function Request(requestInit) {
  this.method = requestInit.method || "GET";
  this.body = requestInit.body;
  this.headers = requestInit.headers;
}

function fetch(url, requestInit) {
  return new Promise(function(resolve, reject) {
    const request = new XMLHttpRequest();
    const requestConfiguration = new Request(requestInit || {});
    request.open(requestConfiguration.method, url);
    request.onload = function() {
      const response = new Response(this);
      resolve(response);
    };
    request.onerror = function() {
      reject("Network error!");
    };
    for (const header in requestConfiguration.headers) {
      request.setRequestHeader(header, requestConfiguration.headers[header]);
    }
    requestConfiguration.body && request.send(requestConfiguration.body);
    requestConfiguration.body || request.send();
  });
}

const link = {
  title: "Building a Fetch Polyfill From Scratch",
  url: "https://www.valentinog.com/fetch-api/"
};

const requestInit = {
  method: "POST",
  headers: {
    "Content-Type": "application/json"
  },
  body: JSON.stringify(link)
};

fetch("https://academy.valentinog.com/api/link/create/", requestInit)
  .then(function(response) {
    if (!response.ok) {
      throw Error(response.statusText);
    }
    return response.json();
  })
  .then(function(json) {
    console.log(json);
  })
  .catch(function(error) {
    console.log(error);
  });


실제 Fetch API 구현은 훨씬 더 복잡하며 고급 기능을 지원합니다. 우리는 여기서 표면을 긁었습니다. 예를 들어 헤더를 추가하는 논리는 자체적으로 메서드를 사용할 수 있습니다. 또한 PUT 및 DELETE 지원 및 다양한 형식으로 응답을 반환하기 위한 더 많은 기능 등 새로운 기능을 추가 할 여지가 많이 있습니다. 더 복잡한 Fetch API 폴리필이 궁금하다면 Github의 엔지니어로부터 whatwg-fetch를 확인하십시오. 수제 폴리필과 많은 유사점이 있습니다.


결론 


AJAX는 유동적이고 사용자 친화적인 인터페이스를 구축 할 수 있는 기회를 제공함으로써 웹 구축 방식을 결정했습니다. 고전적인 페이지 새로 고침 시절은 오래 걸렸습니다. 오늘날 우리는 우아한 JavaScript 애플리케이션을 구축하고 필요한 데이터를 백그라운드에서 얻을 수 있습니다. XMLHttpRequest는 HTTP 요청을 작성하기 위한 좋은 기존 레거시 API입니다. 오늘날에도 여전히 사용되고 있지만 다른 화신인 Fetch API입니다. 이 장의 긴 여정이었으며 Fetch API에 대한 깊이와 자체 맞춤형 약속을 사용하는 방법을 보았습니다. jQuery를 사용해 본 적이 있습니까? 이 장을 마치면 자신의 데이터 가져 오기 모듈을 작성하고 jQuery.get() 및 jQuery.getJSON()을 버릴 수 있는 모든 도구를 사용할 수 있습니다.