WeniVooks

검색

JavaScript 에센셜

fetch API

1. Fetch API

XMLHttpRequest를 대체할 새로운 API가 바로 fetch 입니다. fetch는 여러가지 뜻이 있지만 여기서는 ‘가져오다, 찾아내다’의 의미로 사용되고 있습니다. fetch API를 이용하여 손쉽게 AJAX 통신을 할 수 있습니다.

fetch는 네트워크 요청을 보내는 메소드로, XHR과 비슷한 역할을 합니다. 하지만 XHR과 fetch의 가장 큰 차이점은 XMLHttpRequest가 생성하는 인스턴스는 통신의 기능을 수행하는 XMLHttpRequest 객체를 반환했습니다.

Untitled

fetch는 인스턴스를 만들지 않고, 대신 Promise을 반환한다는 것입니다.

Untitled

1.1 Promise

Promise는 이름에서 알 수 있듯이 약속을 하는 것입니다. 그렇다면 무엇을 약속할까요?

우리가 스타벅스에서 커피를 주문하는 상황을 생각해봅시다.

  1. 스타벅스에 가서 커피를 주문할 때, 카운터에서 잠시 고민을 하겠죠?
  2. 주문을 받은 점원은 우리에게 “대략 5 ~ 10분 정도 커피를 내리는 시간이 필요합니다. 완료되면 알려드리겠습니다._” 라고 얘기할겁니다. 우리에게 약속(promise) 하는 것입니다. 이 약속은 대기 중인 상태가 됩니다.(pending) 이때 약속은 우리에게 커피를 주거나 또는 못 주거나에 대한 결과를 알려준다는 것 입니다.
  3. 우리는 커피가 만들어지는 동안(Asynchronously) 공부하기 적당한 테이블을 찾고, 노트북을 꺼내고, 주문이 완료되기를 기다립니다.

4.1. 잠시 뒤 커피가 만들어져 나옵니다! 즉, 우리의 주문이 해결되었고(resolved), 약속이 이루어졌습니다. (fulfilled : 약속을 이행, 완수)

4.2. 그런데 어쩌면 이런 상황이 발생할 수도 있습니다. 점원이 급하게 와서 사용할 원두가 떨어졌다고 알려줄 수도 있겠죠. 그렇다면 약속이 이뤄지지 않은 즉, 거절된 상태(rejected) 가 됩니다.

이처럼 Promise를 이용하여 비동기 연산을 다룰 수 있습니다. 비동기 작업의 결과는 즉시 알 수 없지만 '결과가 준비되면 이행되거나 거절될 것이라고 약속을 하는 것'입니다. 위의 예시를 Promise의 세 가지 상태로 다시 나타내면 다음과 같습니다.

  • 대기 (Pending) -> resolve() -> 성공 (Fulfilled)
  • 대기 (Pending) -> reject() -> 실패 (Rejected)

위의 예시에 나온 키워드들을 잘 생각해보면서 실제 코드로 작성해 보겠습니다.

// 커피를 주문하는 프로미스 객체를 생성합니다. 생성자에는 약속을 지키기 위한 resolve와, 약속을 지키지 못했을 때를 대비한 reject 두 가지를 인자로 전달합니다.
// 프로미스 객체를 생성하는 순간 프로미스 생성자함수의 콜백 함수가 실행됩니다. 이를 실행자(executor)라 부릅니다.
const orderCoffee = new Promise((resolve, reject) => {
  const requestObj = new XMLHttpRequest();
  requestObj.open('GET', 'http://test.api.weniv.co.kr/');
  requestObj.onreadystatechange = () => {
    if (requestObj.readyState === 4) {
      if (requestObj.status === 200) {
        const result = requestObj.responseText;
        // resolve 메소드가 실행되면 then 메소드가 자동으로 호출됩니다.
        resolve(result);
      } else {
        // resolve 메소드 호출이 없는 상태에서 reject 메소드가 실행되면 catch 메소드가 자동으로 호출됩니다.
        reject(
          new Error(
            `커피주문이 정상적으로 이뤄지지 않았습니다.: ${requestObj.status}`,
          ),
        );
      }
    }
  };
  requestObj.send();
});
 
// then 메소드를 사용하면 비동기 코드를 마치 동기적인 코드처럼 작성할 수 있습니다. 앞에서 작성한 XHR 코드와 비교해보는것도 좋습니다.
// resolve 메소드가 실행될때 전달된 인자는 then 메소드의 콜백함수의 인자로 전달됩니다.
orderCoffee
  .then((asyncResult) => {
    console.log(asyncResult);
    console.log('약속이 이루어졌습니다.');
    return asyncResult;
  })
  .catch((error) => {
    // then 메소드는 프라미스 객체를 반환하기 때문에 catch 메소드를 이어서 쓰는것이 가능합니다.
    // resolve 메소드와 마찬가지로 reject 메소드가 실행될때 전달된 인자는 catch 메소드의 콜백함수의 인자로 전달됩니다.
    console.log(error);
  });
// 커피를 주문하는 프로미스 객체를 생성합니다. 생성자에는 약속을 지키기 위한 resolve와, 약속을 지키지 못했을 때를 대비한 reject 두 가지를 인자로 전달합니다.
// 프로미스 객체를 생성하는 순간 프로미스 생성자함수의 콜백 함수가 실행됩니다. 이를 실행자(executor)라 부릅니다.
const orderCoffee = new Promise((resolve, reject) => {
  const requestObj = new XMLHttpRequest();
  requestObj.open('GET', 'http://test.api.weniv.co.kr/');
  requestObj.onreadystatechange = () => {
    if (requestObj.readyState === 4) {
      if (requestObj.status === 200) {
        const result = requestObj.responseText;
        // resolve 메소드가 실행되면 then 메소드가 자동으로 호출됩니다.
        resolve(result);
      } else {
        // resolve 메소드 호출이 없는 상태에서 reject 메소드가 실행되면 catch 메소드가 자동으로 호출됩니다.
        reject(
          new Error(
            `커피주문이 정상적으로 이뤄지지 않았습니다.: ${requestObj.status}`,
          ),
        );
      }
    }
  };
  requestObj.send();
});
 
// then 메소드를 사용하면 비동기 코드를 마치 동기적인 코드처럼 작성할 수 있습니다. 앞에서 작성한 XHR 코드와 비교해보는것도 좋습니다.
// resolve 메소드가 실행될때 전달된 인자는 then 메소드의 콜백함수의 인자로 전달됩니다.
orderCoffee
  .then((asyncResult) => {
    console.log(asyncResult);
    console.log('약속이 이루어졌습니다.');
    return asyncResult;
  })
  .catch((error) => {
    // then 메소드는 프라미스 객체를 반환하기 때문에 catch 메소드를 이어서 쓰는것이 가능합니다.
    // resolve 메소드와 마찬가지로 reject 메소드가 실행될때 전달된 인자는 catch 메소드의 콜백함수의 인자로 전달됩니다.
    console.log(error);
  });

정리해보면, 프로미스는 비동기 코드를 마치 동기적인 코드처럼 작성할 수 있습니다. 우리는 약속을 의미하는 Promise 객체를 만들었고, 이 약속은 이행되거나(fulfilled), 거절되거나(reject) 둘 중에 한 가지 결과만을 가지게 될겁니다. (물론 두 가지 모두 이뤄지지 않는다면 계속 pending(대기중) 상태가 되겠지만, 실제로 이렇게 코드를 작성할 일은 없겠죠?)

때문에 통신의 결과는 코드상에서는 아직 알 수 없지만, ‘이행되거나 거절되거나 둘 중의 하나의 결과는 전달될거라 약속하고 작업을 진행하자!’ 라는 개념으로 만들어졌기 때문에 then과 catch를 이용해 동기적으로 코드를 이어 쓸 수 있는것 입니다.

비동기(콜백함수, 프로미스, await/async, fetch) 알잘딱깔센 JavaScript 비동기 프로그래밍 - 비동기 너 내 동기가 돼라
2.1 Fetch API 사용해보기

fetch의 기본 사용법은 다음과 같습니다. fetch 함수는 첫 번째 인자로 URL을 받고, 두 번째 인자로 옵션 객체를 받습니다. 옵션 객체에는 네트워크 요청에 대한 설정 정보를 담고 있습니다. 이렇게 fetch 함수를 호출하면 Promise 객체가 반환됩니다.

fetch(url);
fetch(url, option);
fetch(url);
fetch(url, option);

option을 생략하면 자동으로 GET 요청을 보내게 됩니다. option 객체에는 method, headers, body 등의 속성을 설정할 수 있습니다.

속성설명
methodHTTP 요청 메소드를 지정합니다. GET, POST, PUT, DELETE 등이 있습니다.
headersHTTP 요청의 헤더로, 메타데이터를 지정합니다.
bodyHTTP 요청의 바디로, 전송하려는 실제 데이터를 포함합니다.
// GET
fetch('https://eduapi.weniv.co.kr/1/login_user_info');
 
// POST
fetch('https://eduapi.weniv.co.kr/1/signup', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    username: 'weniv',
    password: '1234',
  }),
});
// GET
fetch('https://eduapi.weniv.co.kr/1/login_user_info');
 
// POST
fetch('https://eduapi.weniv.co.kr/1/signup', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    username: 'weniv',
    password: '1234',
  }),
});

fetch를 사용하여 JSON 데이터를 가져오는 예제를 살펴보겠습니다.

let result = fetch(
  'https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json',
);
console.log(result);
let result = fetch(
  'https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json',
);
console.log(result);

fetch는 함수처럼 바로 실행할 수 있습니다. XHR 객체에 비해 무척 간단하죠? fetch 함수를 실행하면 바로 인자로 전달한 url에 접근해 데이터를 요청하게 됩니다. 그러나 fetch 함수는 즉시 프로미스를 반환합니다. 이 프로미스는 네트워크 요청이 완료되면 fulfilled 상태가 됩니다. 반대로 fetch 함수는 네트워크 오류가 발생하면 rejected 상태의 프로미스를 반환합니다.

result를 콘솔창에 출력해보면 promise 객체가 보입니다. 비동기 처리를 위해 then과 catch 메소드를 사용할 수 있습니다.

let result = fetch(
  'https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json',
);
 
result
  .then((data) => {
    console.log(data);
  })
  .catch((error) => {
    console.error(error);
  });
let result = fetch(
  'https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json',
);
 
result
  .then((data) => {
    console.log(data);
  })
  .catch((error) => {
    console.error(error);
  });

fulfilled 상태의 프라미스를 반환하기 때문에 then을 통해 데이터를 확인할 수 있습니다. 콘솔창에 응답 결과를 출력해보면 Response 라는 객체가 출력됩니다. Response는 요청에 대한 종합적인 응답 정보가 저장되어 있는 객체 입니다.

우리가 필요한것은 JSON 데이터이기 때문에 .json() 메소드를 통해 응답 본문을 JSON 형식으로 파싱할 수 있습니다. json() 메서드는 비동기적을로 동작하며, 파싱된 JSON 데이터를 resolve하는 프로미스 객체를 반환합니다. 따라서 then 메서드를 이용하여 파싱된 데이터를 처리할 수 있습니다.

fetch(
  'https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json',
)
  .then((response) => {
    // response.ok 는 응답이 성공적(200-299)일 경우 true, 아니면 false를 반환합니다.
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }
    return response.json();
  })
  .then((data) => {
    console.log(data);
    return data;
  })
  .catch((error) => {
    console.error(error);
  });
fetch(
  'https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json',
)
  .then((response) => {
    // response.ok 는 응답이 성공적(200-299)일 경우 true, 아니면 false를 반환합니다.
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }
    return response.json();
  })
  .then((data) => {
    console.log(data);
    return data;
  })
  .catch((error) => {
    console.error(error);
  });

response.ok response.ok 는 응답이 성공적(200-299)일 경우 true, 아닌 경우 false를 반환합니다. fetch는 네트워크 통신이 완료되면 fulfilled 상태의 프로미스가 반환되는데 왜 이런 예외처리가 필요할까요? fetch에서 fulfilled 상태의 프로미스가 반환되는 것은 네트워크 통신이 성공적으로 완료되었다는 것을 의미합니다. 이는 단순히 서버로부터 응답을 받았음을 의미합니다. 즉, 404나 500과 같은 HTTP 응답 코드가 반환되었을 때도 fulfilled 상태의 프로미스가 반환됩니다. 개발자가 의도한 데이터를 응답받지 못했어도 서버와의 통신 자체는 성공했기 때문에 약속이 이행되었다고 판단됩니다. 따라서 사용자가 의도한 데이터를 무사히 전달받기 위해서는 response.ok를 이용한 예외처리가 필수적입니다.

10.3 XMLHttpRequest10.5 async, await