모든 강의를 무료로 볼 수 있어요. 회원가입 없이도 학습 가능합니다.

26강 / 전체 30강

fetch로 API 호출

14분 읽기 조회 0

fetch API로 GET·POST 요청, 응답 상태 코드 처리, JSON 직렬화, 요청 헤더와 인증, 로딩·오류 상태 UI 패턴, AbortController로 요청 취소까지 실전 HTTP 통신을 완성합니다.

🎯 학습 목표

이 강을 마치면 다음을 할 수 있습니다.

  • fetch로 GET 요청을 보내고 JSON 응답을 파싱할 수 있습니다.
  • fetch로 POST 요청을 보내고 데이터를 서버에 전송할 수 있습니다.
  • HTTP 상태 코드를 확인하고 오류를 적절히 처리할 수 있습니다.
  • 로딩·성공·오류 세 가지 UI 상태를 관리할 수 있습니다.

🌐 fetch API 기초

이 섹션에서는 브라우저 내장 fetch API의 기본 사용법을 살펴보겠습니다.

fetch API는 브라우저에 내장된 HTTP 통신 인터페이스입니다. 과거에는 XMLHttpRequest(XHR)를 사용했지만, fetch가 더 직관적인 Promise 기반 API를 제공해 현재 표준이 되었습니다. fetch는 URL을 인수로 받아 Promise를 반환합니다. 기본적으로 GET 요청을 보냅니다.

// 기본 GET 요청
fetch('https://jsonplaceholder.typicode.com/users/1')
  .then(response => {
    console.log(response.status);      // 200
    console.log(response.ok);          // true (200-299)
    console.log(response.statusText);  // 'OK'
    return response.json();            // 응답 본문을 JSON으로 파싱
  })
  .then(user => {
    console.log(user.name);  // 'Leanne Graham'
  })
  .catch(err => {
    // 네트워크 오류(인터넷 끊김 등)만 catch
    // HTTP 오류(404, 500)는 catch 안 됨!
    console.error('네트워크 오류:', err);
  });

// async/await 방식 (권장)
async function getUser(id) {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/users/' + id);

    // fetch는 HTTP 오류(404, 500)에도 reject하지 않음
    // response.ok를 반드시 확인해야 함
    if (!response.ok) {
      throw new Error('HTTP 오류: ' + response.status);
    }

    const user = await response.json();
    return user;
  } catch (error) {
    console.error('오류:', error.message);
    throw error;
  }
}

const user = await getUser(1);
console.log(user.name);

fetch에서 가장 중요한 주의사항: fetch는 HTTP 오류 상태 코드(404, 500 등)에도 Promise를 reject하지 않습니다. 네트워크 연결 자체가 끊어진 경우만 reject됩니다. 따라서 response.ok(200~299이면 true)를 반드시 확인하고, false이면 수동으로 오류를 throw해야 합니다. response.json()도 Promise를 반환하므로 await가 필요합니다. 응답 본문을 읽는 메서드는 .json() 외에도 .text()(문자열), .blob()(파일), .arrayBuffer()(바이너리)가 있습니다.

📤 POST 요청 — 데이터 전송

이 섹션에서는 서버에 데이터를 전송하는 POST 요청 작성법을 살펴보겠습니다.

GET 요청은 데이터를 조회할 때, POST 요청은 새로운 데이터를 생성하거나 서버에 데이터를 전송할 때 사용합니다. fetch의 두 번째 인수인 options 객체로 메서드, 헤더, 본문을 설정합니다.

// POST 요청 — JSON 데이터 전송
async function createUser(userData) {
  const response = await fetch('https://jsonplaceholder.typicode.com/users', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',  // JSON 전송 시 필수
    },
    body: JSON.stringify(userData),  // JS 객체 → JSON 문자열
  });

  if (!response.ok) {
    throw new Error('생성 실패: ' + response.status);
  }

  return response.json();  // 서버가 반환한 생성된 데이터
}

const newUser = await createUser({
  name: '홍길동',
  email: 'hong@example.com',
  username: 'hong'
});
console.log(newUser.id);  // 서버가 할당한 ID

// PUT — 전체 데이터 수정
async function updateUser(id, data) {
  const response = await fetch('/api/users/' + id, {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data),
  });
  if (!response.ok) throw new Error('수정 실패');
  return response.json();
}

// PATCH — 일부 데이터 수정
async function patchUser(id, changes) {
  const response = await fetch('/api/users/' + id, {
    method: 'PATCH',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(changes),
  });
  if (!response.ok) throw new Error('수정 실패');
  return response.json();
}

// DELETE
async function deleteUser(id) {
  const response = await fetch('/api/users/' + id, {
    method: 'DELETE',
  });
  if (!response.ok) throw new Error('삭제 실패');
  // DELETE 응답에 본문이 없을 수 있음
  return response.status === 204 ? null : response.json();
}

REST API에서 HTTP 메서드의 역할을 정리하면 이렇습니다. GET: 조회, POST: 생성, PUT: 전체 수정, PATCH: 일부 수정, DELETE: 삭제. Content-Type: application/json 헤더는 서버에게 전송 데이터가 JSON임을 알립니다. 이 헤더가 없으면 서버가 본문을 제대로 파싱하지 못할 수 있습니다. body는 문자열이어야 하므로 JSON.stringify()로 변환합니다.

🔐 요청 헤더와 인증

이 섹션에서는 API 인증에 필요한 헤더를 설정하는 방법을 살펴보겠습니다.

대부분의 실제 API는 인증이 필요합니다. 가장 일반적인 방식은 Bearer 토큰을 Authorization 헤더에 담아 전송하는 것입니다.

// Authorization 헤더로 인증
async function fetchWithAuth(url, options = {}) {
  const token = localStorage.getItem('accessToken');

  const response = await fetch(url, {
    ...options,
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer ' + token,  // JWT 토큰
      ...options.headers,  // 추가 헤더
    },
  });

  // 401: 인증 만료
  if (response.status === 401) {
    // 토큰 갱신 후 재시도 로직
    await refreshToken();
    return fetchWithAuth(url, options);
  }

  if (!response.ok) throw new Error('HTTP ' + response.status);
  return response.json();
}

// 래퍼 함수로 편리하게 사용
const api = {
  get:    (url)          => fetchWithAuth(url),
  post:   (url, data)    => fetchWithAuth(url, { method: 'POST', body: JSON.stringify(data) }),
  put:    (url, data)    => fetchWithAuth(url, { method: 'PUT',  body: JSON.stringify(data) }),
  delete: (url)          => fetchWithAuth(url, { method: 'DELETE' }),
};

// 사용
const users = await api.get('/api/users');
const newUser = await api.post('/api/users', { name: '홍길동' });

⏳ 로딩·오류 UI 상태 관리

이 섹션에서는 API 호출 중 사용자에게 적절한 피드백을 제공하는 로딩·성공·오류 상태 패턴을 살펴보겠습니다.

실제 앱에서 API 호출은 세 가지 상태를 거칩니다. 로딩 중(Loading): 요청 중 스피너 표시. 성공(Success): 데이터 렌더링. 오류(Error): 오류 메시지 표시. 이 세 가지를 모두 처리해야 좋은 UX를 제공할 수 있습니다.

// HTML:
// <div id="loading" class="hidden">로딩 중...</div>
// <div id="error"  class="hidden"></div>
// <ul id="user-list"></ul>

const loadingEl  = document.querySelector('#loading');
const errorEl    = document.querySelector('#error');
const userListEl = document.querySelector('#user-list');

async function loadUsers() {
  // 로딩 상태 시작
  loadingEl.classList.remove('hidden');
  errorEl.classList.add('hidden');
  userListEl.innerHTML = '';

  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/users');

    if (!response.ok) {
      throw new Error('서버 오류: ' + response.status);
    }

    const users = await response.json();

    // 성공: 데이터 렌더링
    users.forEach(user => {
      const li = document.createElement('li');
      li.textContent = user.name + ' (' + user.email + ')';
      userListEl.append(li);
    });

  } catch (error) {
    // 오류 표시
    errorEl.textContent = '데이터를 불러오지 못했습니다: ' + error.message;
    errorEl.classList.remove('hidden');
    console.error(error);

  } finally {
    // 로딩 종료 (성공/실패 모두)
    loadingEl.classList.add('hidden');
  }
}

loadUsers();

🚫 AbortController — 요청 취소

이 섹션에서는 진행 중인 fetch 요청을 취소하는 방법을 살펴보겠습니다.

검색 자동완성처럼 사용자가 빠르게 타이핑하면 이전 요청이 불필요해집니다. AbortController로 진행 중인 요청을 취소할 수 있습니다.

let controller = null;

const searchInput = document.querySelector('#search');

searchInput.addEventListener('input', async (e) => {
  const query = e.target.value.trim();
  if (!query) return;

  // 이전 요청 취소
  if (controller) controller.abort();
  controller = new AbortController();

  try {
    const response = await fetch('/api/search?q=' + encodeURIComponent(query), {
      signal: controller.signal  // AbortController 연결
    });

    if (!response.ok) throw new Error('검색 실패');
    const results = await response.json();
    renderResults(results);

  } catch (error) {
    if (error.name === 'AbortError') {
      // 취소는 오류가 아님 — 무시
      return;
    }
    console.error('검색 오류:', error.message);
  }
});

// 타임아웃과 AbortController 결합
async function fetchWithTimeout(url, ms = 5000) {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), ms);

  try {
    const response = await fetch(url, { signal: controller.signal });
    clearTimeout(timeoutId);
    if (!response.ok) throw new Error('HTTP ' + response.status);
    return response.json();
  } catch (error) {
    if (error.name === 'AbortError') {
      throw new Error('요청 타임아웃 (' + ms + 'ms)');
    }
    throw error;
  }
}

📝 핵심 요약

26강에서 배운 내용을 정리해 보겠습니다.

개념핵심 내용
기본 GETfetch(url) → response.ok 확인 → response.json()
POSTmethod: 'POST', Content-Type: application/json, body: JSON.stringify(data)
오류 처리fetch는 HTTP 오류에서 reject 안 함 → response.ok 수동 확인 필수
인증Authorization: Bearer <token> 헤더
로딩 상태로딩 시작 → try/catch → finally에서 로딩 종료
요청 취소AbortController + signal 옵션

다음 강인 27강 — 로컬 스토리지에서는 브라우저에 데이터를 영구 저장하는 방법을 배웁니다. 로그인 상태, 사용자 설정, 장바구니처럼 서버 없이 브라우저에 저장해야 하는 데이터는 localStorage와 sessionStorage로 관리합니다!

관련 주제

  • fetch API
  • GET POST 요청
  • HTTP 상태 코드
  • JSON 파싱
  • AbortController
  • 로딩 에러 상태
  • 개발·프로그래밍
  • 개발·프로그래밍 강의
  • 자바스크립트 기초 30강 — 웹을 움직이는 언어
  • 무료강의
  • 무료 온라인 강의
  • NUGUNA
  • 누구나

📚 시리즈 전체 공유

자바스크립트 기초 30강 — 웹을 움직이는 언어

이 강의가 속한 시리즈는 총 30강, 모두 무료입니다. 처음부터 배우려는 동료에게 시리즈 전체를 알려 주세요.

댓글

0/1000

불러오는 중...