2020년 8월 24일

fetch

자바스크립트를 사용하면 필요할 때 서버에 네트워크 요청을 보내고 새로운 정보를 받아오는 일을 할 수 있습니다.

네트워크 요청은 다음과 같은 경우에 이뤄집니다.

  • 주문 전송
  • 사용자 정보 읽기
  • 서버에서 최신 변경분 가져오기
  • 등등

그런데 이 모든 것들은 페이지 새로 고침 없이도 가능합니다.

AJAX(Asynchronous JavaScript And XML, 비동기적 JavaScript와 XML)라는 용어를 들어보신 분이 있으실 겁니다. AJAX는 서버에서 추가 정보를 비동기적으로 가져올 수 있게 해주는 포괄적인 기술을 나타내는 용어로, 만들어진 지 오래되었습니다. AJAX에 XML이 포함된 이유가 바로 이 때문이죠.

AJAX 이외에도 서버에 네트워크 요청을 보내고 정보를 받아올 수 있는 방법은 다양합니다.

그중 이번 챕터에선 모던하고 다재다능한 fetch() 메서드에 대해 소개해드리려 합니다. fetch()는 구식 브라우저에선 지원하진 않지만(폴리필을 쓰면 사용 가능) 대부분의 모던 브라우저가 지원합니다.

fetch() 기본 문법은 다음과 같습니다.

let promise = fetch(url, [options])
  • url – 접근하고자 하는 URL
  • options – 선택 매개변수, method나 header 등을 지정할 수 있음

options에 아무것도 넘기지 않으면 요청은 GET 메서드로 진행되어 url로부터 콘텐츠가 다운로드 됩니다.

fetch()를 호출하면 브라우저는 네트워크 요청을 보내고 프라미스가 반환됩니다. 반환되는 프라미스는 fetch()를 호출하는 코드에서 사용됩니다.

응답은 대개 두 단계를 거쳐 진행됩니다.

먼저, 서버에서 응답 헤더를 받자마자 fetch 호출 시 반환받은 promise가 내장 클래스 Response의 인스턴스와 함께 이행 상태가 됩니다.

이 단계는 아직 본문(body)이 도착하기 전이지만, 개발자는 응답 헤더를 보고 요청이 성공적으로 처리되었는지 아닌지를 확인할 수 있습니다.

네트워크 문제나 존재하지 않는 사이트에 접속하려는 경우같이 HTTP 요청을 보낼 수 없는 상태에선 프라미스는 거부상태가 됩니다.

HTTP 상태는 응답 프로퍼티를 사용해 확인할 수 있습니다.

  • status – HTTP 상태 코드(예: 200)
  • ok – 불린 값. HTTP 상태 코드가 200과 299 사이일 경우 true

예시:

let response = await fetch(url);

if (response.ok) { // HTTP 상태 코드가 200~299일 경우
  // 응답 몬문을 받습니다(관련 메서드는 아래에서 설명).
  let json = await response.json();
} else {
  alert("HTTP-Error: " + response.status);
}

두 번째 단계에선 추가 메서드를 호출해 응답 본문을 받습니다.

response 에는 프라미스를 기반으로 하는 다양한 메서드가 있습니다. 이 메서드들을 사용하면 다양한 형태의 응답 본문을 처리할 수 있습니다.

  • response.text() – 응답을 읽고 텍스트를 반환합니다,
  • response.json() – 응답을 JSON 형태로 파싱합니다,
  • response.formData() – 응답을 FormData 객체 형태로 반환합니다. FormData에 대한 자세한 내용은 다음 챕터에서 다루겠습니다.
  • response.blob() – 응답을 Blob(타입이 있는 바이너리 데이터) 형태로 반환합니다.
  • response.arrayBuffer() – 응답을 ArrayBuffer(바이너리 데이터를 로우 레벨 형식으로 표현한 것) 형태로 반환합니다.
  • 이 외에도 response.body가 있는데, ReadableStream 객체인 response.body를 사용하면 응답 본문을 청크 단위로 읽을 수 있습니다. 자세한 용례는 곧 살펴보겠습니다.

지금까지 배운 내용을 토대로 GitHub에서 마지막 커밋을 JSON 객체 형태로 받아봅시다.

let url = 'https://api.github.com/repos/javascript-tutorial/ko.javascript.info/commits';
let response = await fetch(url);

let commits = await response.json(); // 응답 본문을 읽고 JSON 형태로 파싱함

alert(commits[0].author.login);

위 예시를 await 없이 프라미스만 사용하면 다음과 같이 바꿀 수 있습니다.

fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits')
  .then(response => response.json())
  .then(commits => alert(commits[0].author.login));

응답을 텍스트 형태로 얻으려면 .json() 대신 await response.text()를 사용하면 됩니다.

let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits');

let text = await response.text(); // 응답 본문을 텍스트 형태로 읽습니다.

alert(text.slice(0, 80) + '...');

이번엔 fetch를 사용해 fetch 명세서 우측 상단에 있는 로고(바이너리 데이터)를 가져와 보겠습니다. 참고로 Blob에 대한 자세한 내용은 링크에서 살펴볼 수 있습니다.

let response = await fetch('/article/fetch/logo-fetch.svg');

let blob = await response.blob(); // 응답을 Blob 객체 형태로 다운로드받습니다.

// 다운로드받은 Blob을 담을 <img>를 만듭니다.
let img = document.createElement('img');
img.style = 'position:fixed;top:10px;left:10px;width:100px';
document.body.append(img);

// 이미지를 화면에 보여줍니다.
img.src = URL.createObjectURL(blob);

setTimeout(() => { // 3초 후 이미지를 숨깁니다.
  img.remove();
  URL.revokeObjectURL(img.src);
}, 3000);
중요:

본문을 읽을 때 사용되는 메서드는 딱 하나만 사용할 수 있습니다.

response.text()를 사용해 응답을 얻었다면 본문의 콘텐츠는 모두 처리 된 상태이기 때문에 response.json()은 동작하지 않습니다.

let text = await response.text(); // 응답 본문이 소비됩니다.
let parsed = await response.json(); // 실패

응답 헤더

응답 헤더는 response.headers에 맵과 유사한 형태로 저장됩니다.

맵은 아닙니다. 하지만 맵과 유사한 메서드를 지원하죠. 이 메서드들을 사용하면 헤더 일부만 추출하거나 헤더 전체를 순회할 수 있습니다.

let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits');

// 헤더 일부를 추출
alert(response.headers.get('Content-Type')); // application/json; charset=utf-8

// 헤더 전체를 순회
for (let [key, value] of response.headers) {
  alert(`${key} = ${value}`);
}

요청 헤더

headers 옵션을 사용하면 fetch에 요청 헤더를 설정할 수 있습니다. headers엔 아래와 같이 다양한 헤더 정보가 담긴 객체를 넘기게 됩니다.

let response = fetch(protectedUrl, {
  headers: {
    Authentication: 'secret'
  }
});

그런데 headers를 사용해 설정할 수 없는 헤더도 있습니다. 금지된 헤더 전체 목록은 링크에서 확인할 수 있습니다.

  • Accept-Charset, Accept-Encoding
  • Access-Control-Request-Headers
  • Access-Control-Request-Method
  • Connection
  • Content-Length
  • Cookie, Cookie2
  • Date
  • DNT
  • Expect
  • Host
  • Keep-Alive
  • Origin
  • Referer
  • TE
  • Trailer
  • Transfer-Encoding
  • Upgrade
  • Via
  • Proxy-*
  • Sec-*

이런 제약은 HTTP를 목적에 맞고 안전하게 사용할 수 있도록 하려고 만들어졌습니다. 금지 목록에 있는 헤더는 브라우저만 배타적으로 설정, 관리할 수 있습니다.

POST 요청

GET 이외의 요청을 보내려면 추가 옵션을 사용해야 합니다.