🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

fetch 기본 사용법 완벽 가이드 - 슬라이드 1/7
A

AI Generated

2025. 12. 10. · 19 Views

fetch 기본 사용법 완벽 가이드

웹 개발의 필수 기능인 fetch API의 기본 개념부터 실전 활용까지 초급 개발자를 위해 술술 읽히는 스타일로 설명합니다. 실무에서 자주 하는 실수와 해결법, 그리고 깔끔한 코드 작성 방법까지 다룹니다.


목차

  1. fetch이란
  2. 기본 문법과 사용법
  3. 간단한 예제 코드
  4. 실제 소스 코드 분석
  5. 자주 하는 실수와 해결법
  6. 실전 활용 팁

1. fetch이란

어느 날 신입 개발자 김개발 씨는 웹 페이지에 날씨 정보를 보여주는 기능을 만들어야 했습니다. 서버에서 데이터를 가져와야 하는데, 어떻게 해야 할지 막막했습니다.

선배 개발자 박시니어 씨가 다가와 말했습니다. "fetch를 사용해보세요."

fetch는 웹 브라우저에서 서버와 통신하기 위해 사용하는 최신 API입니다. 마치 도서관 사서에게 책을 요청하면 찾아주는 것처럼, fetch는 서버에 데이터를 요청하고 받아오는 역할을 합니다.

예전에 사용하던 XMLHttpRequest보다 훨씬 간결하고 직관적이어서 현대 웹 개발의 표준이 되었습니다.

다음 코드를 살펴봅시다.

// 기본적인 fetch 사용법
fetch('https://api.example.com/data')
  .then(response => response.json())  // 응답을 JSON으로 변환
  .then(data => {
    console.log('받아온 데이터:', data);
  })
  .catch(error => {
    console.error('에러 발생:', error);
  });

김개발 씨는 입사 1개월 차 신입 개발자입니다. 오늘 팀장님으로부터 날씨 정보를 보여주는 위젯을 만들라는 미션을 받았습니다.

문제는 날씨 데이터를 어디선가 가져와야 한다는 것입니다. "선배님, 서버에서 데이터를 가져오려면 어떻게 해야 하나요?" 김개발 씨가 박시니어 씨에게 물었습니다.

박시니어 씨는 웃으며 대답했습니다. "fetch를 사용하면 됩니다.

요즘은 모두 이걸 사용하죠." fetch란 무엇일까요? 쉽게 비유하자면, fetch는 마치 우체부와 같습니다. 우리가 편지를 우체통에 넣으면 우체부가 목적지까지 전달하고 답장을 가져오듯이, fetch는 우리의 요청을 서버에 보내고 응답을 가져옵니다.

이 모든 과정이 코드 몇 줄로 간단하게 처리됩니다. fetch가 없던 시절에는 어땠을까요? 옛날 웹 개발자들은 XMLHttpRequest라는 복잡한 방식을 사용했습니다.

코드가 길고 복잡했으며, 콜백 함수가 중첩되어 읽기도 어려웠습니다. "콜백 지옥"이라는 말이 있을 정도였습니다.

더 큰 문제는 에러 처리가 까다로워서 디버깅하기도 힘들었다는 점입니다. fetch의 등장 바로 이런 문제를 해결하기 위해 fetch API가 등장했습니다.

fetch를 사용하면 훨씬 간결한 코드를 작성할 수 있습니다. 또한 Promise 기반으로 동작하기 때문에 비동기 처리가 깔끔해집니다.

무엇보다 현대적인 async/await 문법과 완벽하게 호환된다는 큰 이점이 있습니다. 코드 분석 위의 코드를 한 줄씩 살펴보겠습니다.

먼저 첫 번째 줄의 fetch 함수는 서버에 요청을 보내는 역할을 합니다. 괄호 안에 URL을 넣으면 그 주소로 GET 요청이 전송됩니다.

이 함수는 Promise를 반환하므로 then 메서드를 체이닝할 수 있습니다. 두 번째 줄에서는 서버로부터 받은 응답을 JSON 형태로 변환합니다.

response.json() 메서드는 응답 본문을 파싱하여 JavaScript 객체로 만들어줍니다. 세 번째 줄의 then에서는 변환된 데이터를 실제로 사용할 수 있습니다.

마지막 catch 부분은 네트워크 에러나 기타 문제가 발생했을 때 실행됩니다. 에러를 처리하지 않으면 프로그램이 중단될 수 있으므로 반드시 포함해야 합니다.

실무 활용 사례 실제 현업에서는 어떻게 활용할까요? 예를 들어 뉴스 웹사이트를 개발한다고 가정해봅시다.

메인 페이지에 최신 뉴스 목록을 보여줘야 하는데, 이 데이터는 서버에서 가져와야 합니다. fetch를 사용하면 페이지 로딩 시 자동으로 서버에서 최신 뉴스를 받아와 화면에 표시할 수 있습니다.

네이버, 다음 같은 포털 사이트도 모두 이런 방식을 사용합니다. 주의사항 하지만 주의할 점도 있습니다.

초보 개발자들이 흔히 하는 실수 중 하나는 에러 처리를 생략하는 것입니다. 네트워크는 언제든 불안정해질 수 있고, 서버가 응답하지 않을 수도 있습니다.

이럴 때 catch 문이 없으면 사용자에게 아무런 피드백도 주지 못한 채 기능이 멈춰버립니다. 정리 다시 김개발 씨의 이야기로 돌아가 봅시다.

박시니어 씨의 설명을 들은 김개발 씨는 노트북을 열고 직접 코드를 작성해보았습니다. "와, 생각보다 간단하네요!" fetch를 제대로 이해하면 서버와의 통신이 필요한 거의 모든 기능을 구현할 수 있습니다.

여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.

실전 팁

💡 - fetch는 기본적으로 GET 요청을 보냅니다. 다른 HTTP 메서드를 사용하려면 옵션을 추가해야 합니다.

  • 항상 catch 문을 포함하여 에러를 처리하세요. 사용자 경험이 크게 향상됩니다.
  • 최신 프로젝트에서는 fetch와 async/await를 함께 사용하는 것이 일반적입니다.

2. 기본 문법과 사용법

김개발 씨는 첫 번째 fetch 코드를 작성했지만, 뭔가 부족한 느낌이 들었습니다. "선배님, fetch에 다른 옵션도 줄 수 있나요?" 박시니어 씨가 고개를 끄덕이며 말했습니다.

"당연하죠. fetch는 굉장히 유연한 API입니다."

fetch 함수는 첫 번째 인자로 URL을, 두 번째 인자로 옵션 객체를 받습니다. 옵션 객체에는 HTTP 메서드, 헤더, 본문 데이터 등을 지정할 수 있습니다.

마치 택배를 보낼 때 주소뿐만 아니라 배송 방법, 포장 방식, 메모를 추가하는 것처럼, fetch도 다양한 설정을 할 수 있습니다.

다음 코드를 살펴봅시다.

// POST 요청으로 데이터 전송하기
fetch('https://api.example.com/users', {
  method: 'POST',  // HTTP 메서드 지정
  headers: {
    'Content-Type': 'application/json',  // 콘텐츠 타입 설정
    'Authorization': 'Bearer token123'   // 인증 토큰
  },
  body: JSON.stringify({  // 전송할 데이터를 JSON 문자열로 변환
    name: '김개발',
    email: 'kim@example.com'
  })
})
  .then(response => response.json())
  .then(data => console.log('성공:', data))
  .catch(error => console.error('에러:', error));

김개발 씨는 이제 데이터를 가져오는 것뿐만 아니라 서버에 새로운 데이터를 저장해야 하는 상황이 생겼습니다. 회원가입 폼을 만들어야 하는데, 사용자가 입력한 정보를 서버로 전송해야 합니다.

"GET 요청만으로는 부족해요. POST 요청을 보내야 하는데 어떻게 하죠?" 김개발 씨가 고민에 빠졌습니다.

박시니어 씨는 김개발 씨의 화면을 보며 설명을 시작했습니다. "fetch의 두 번째 인자를 활용하면 됩니다." fetch의 두 번째 인자, 옵션 객체 fetch 함수는 사실 두 개의 인자를 받을 수 있습니다.

첫 번째는 우리가 이미 본 URL이고, 두 번째는 옵션 객체입니다. 쉽게 비유하자면, 첫 번째 인자는 "어디로 갈 것인가"를 결정하고, 두 번째 인자는 "어떻게 갈 것인가"를 결정합니다.

택배를 보낼 때 주소만 쓰는 게 아니라 빠른 배송인지, 일반 배송인지, 포장은 어떻게 할지 선택하는 것과 같습니다. 옵션 객체에는 무엇을 넣을 수 있을까요? 가장 많이 사용하는 옵션은 method입니다.

HTTP 메서드를 지정하는 옵션으로, 'GET', 'POST', 'PUT', 'DELETE' 등을 사용할 수 있습니다. 기본값은 'GET'이므로 생략하면 GET 요청이 전송됩니다.

다음으로 중요한 것은 headers입니다. 서버에게 우리가 보내는 데이터의 형식이나 인증 정보를 알려주는 메타데이터입니다.

편지 봉투에 적는 보낸 사람, 받는 사람 정보와 비슷합니다. 마지막으로 body는 실제로 전송할 데이터를 담습니다.

POST나 PUT 요청에서 주로 사용하며, 서버에 저장하거나 수정할 정보를 여기에 넣습니다. 코드 분석 위의 코드를 차근차근 살펴보겠습니다.

method 속성에 'POST'를 지정하여 이것이 데이터를 생성하는 요청임을 명시했습니다. headers 객체에는 두 가지 정보가 들어있습니다.

Content-Type은 우리가 JSON 형식의 데이터를 보낸다는 것을 알려주고, Authorization은 인증 토큰을 포함합니다. body 부분이 핵심입니다.

JavaScript 객체를 직접 보낼 수는 없으므로 JSON.stringify() 함수로 문자열로 변환합니다. 이렇게 하면 서버가 이해할 수 있는 형태가 됩니다.

실무에서의 활용 실제 프로젝트에서는 이런 패턴이 매우 자주 등장합니다. 쇼핑몰 프로젝트를 개발한다고 생각해봅시다.

사용자가 장바구니에 상품을 추가할 때마다 POST 요청을 보내야 합니다. 상품 ID, 수량, 옵션 등의 정보를 body에 담아 서버로 전송하는 것이죠.

카카오, 쿠팡 같은 대형 서비스도 모두 이런 방식을 사용합니다. 헤더의 중요성 headers를 제대로 설정하지 않으면 서버가 요청을 거부할 수 있습니다.

특히 Content-Type을 빠뜨리면 서버가 데이터 형식을 파악하지 못해 에러가 발생합니다. 또한 인증이 필요한 API의 경우 Authorization 헤더가 없으면 401 Unauthorized 에러를 받게 됩니다.

따라서 API 문서를 꼼꼼히 읽고 필요한 헤더를 모두 포함해야 합니다. 정리 김개발 씨는 이제 GET뿐만 아니라 POST 요청도 자유롭게 보낼 수 있게 되었습니다.

"옵션 객체 하나로 이렇게 많은 걸 할 수 있다니!" fetch의 기본 문법을 익히면 RESTful API와 통신하는 모든 작업을 수행할 수 있습니다. 옵션 객체를 적절히 활용하여 다양한 HTTP 요청을 만들어보세요.

실전 팁

💡 - GET 요청에는 body를 포함할 수 없습니다. 쿼리 파라미터를 URL에 직접 추가하세요.

  • JSON 데이터를 보낼 때는 반드시 JSON.stringify()로 변환하고 Content-Type 헤더를 설정하세요.
  • 민감한 정보는 headers에 포함하되, 절대 URL에 직접 노출하지 마세요.

3. 간단한 예제 코드

김개발 씨는 이론은 어느 정도 이해했지만, 실제로 동작하는 코드를 만들어보고 싶었습니다. "선배님, 실전 예제를 하나 만들어볼까요?" 박시니어 씨는 좋은 생각이라며 간단한 할 일 목록 앱을 제안했습니다.

실전 예제를 통해 fetch의 전체 흐름을 이해할 수 있습니다. 할 일 목록을 조회하고, 새로운 할 일을 추가하고, 완료 상태를 업데이트하는 CRUD 작업을 fetch로 구현해봅시다.

마치 실제 서비스를 만드는 것처럼 단계별로 코드를 작성하면 이해가 훨씬 쉬워집니다.

다음 코드를 살펴봅시다.

// 할 일 목록 조회 (GET)
async function getTodos() {
  const response = await fetch('https://api.example.com/todos');
  const todos = await response.json();
  return todos;
}

// 새로운 할 일 추가 (POST)
async function addTodo(title) {
  const response = await fetch('https://api.example.com/todos', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ title, completed: false })
  });
  return await response.json();
}

// 할 일 완료 처리 (PUT)
async function completeTodo(id) {
  const response = await fetch(`https://api.example.com/todos/${id}`, {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ completed: true })
  });
  return await response.json();
}

김개발 씨는 드디어 실전 코드를 작성할 시간입니다. 박시니어 씨가 준비한 예제는 간단한 할 일 목록 관리 시스템입니다.

"가장 기본적인 CRUD 작업을 모두 포함한 예제입니다. 이것만 이해하면 대부분의 API 통신을 할 수 있어요." 박시니어 씨의 설명이 시작되었습니다.

async/await와 함께 사용하기 앞서 본 예제들은 then 메서드 체이닝을 사용했습니다. 하지만 현대적인 JavaScript에서는 async/await 문법을 더 많이 사용합니다.

쉽게 비유하자면, then 체이닝은 마치 계단을 한 칸씩 내려가는 것 같고, async/await는 엘리베이터를 타고 원하는 층으로 바로 이동하는 것 같습니다. 코드가 위에서 아래로 자연스럽게 읽히기 때문에 이해하기 훨씬 쉽습니다.

할 일 조회 함수 첫 번째 함수인 getTodos를 살펴봅시다. 함수 앞에 async 키워드를 붙이면 이 함수가 비동기 함수임을 선언하는 것입니다.

내부에서 await 키워드를 사용하여 fetch가 완료될 때까지 기다립니다. fetch는 Promise를 반환하므로 await를 붙이면 실제 응답 객체를 받을 수 있습니다.

두 번째 줄에서는 response.json()에도 await를 사용합니다. json() 메서드도 Promise를 반환하기 때문입니다.

이렇게 하면 변환된 JavaScript 객체를 바로 사용할 수 있습니다. 할 일 추가 함수 addTodo 함수는 사용자가 입력한 할 일 제목을 서버에 저장합니다.

title 파라미터로 받은 값을 body에 포함시킵니다. completed는 false로 초기화하여 아직 완료되지 않은 상태임을 표시합니다.

POST 요청이므로 method를 명시적으로 지정해야 합니다. 서버는 보통 새로 생성된 데이터를 응답으로 돌려줍니다.

이 응답에는 서버가 자동으로 부여한 ID 같은 정보가 포함되어 있어 나중에 수정이나 삭제 작업에 사용할 수 있습니다. 할 일 완료 처리 함수 completeTodo 함수는 기존 할 일의 상태를 업데이트합니다.

URL에 할 일의 ID를 포함시킨 점이 특징입니다. 템플릿 리터럴을 사용하여 동적으로 URL을 생성합니다.

PUT 메서드는 기존 리소스를 수정할 때 사용하는 HTTP 메서드입니다. 실전 활용 시나리오 이 세 함수를 조합하면 완전한 할 일 관리 앱을 만들 수 있습니다.

페이지가 로드될 때 getTodos()를 호출하여 기존 할 일 목록을 가져옵니다. 사용자가 새로운 할 일을 입력하고 추가 버튼을 클릭하면 addTodo()가 실행됩니다.

체크박스를 클릭하여 할 일을 완료 처리하면 completeTodo()가 호출되는 식입니다. 트렐로, 노션 같은 생산성 도구들이 모두 이런 방식으로 작동합니다.

에러 처리는 어떻게? 예제에서는 간결함을 위해 에러 처리를 생략했지만, 실제 코드에서는 반드시 포함해야 합니다. try-catch 문으로 각 함수를 감싸면 네트워크 에러나 서버 에러를 적절히 처리할 수 있습니다.

사용자에게 "네트워크 연결을 확인해주세요" 같은 친절한 메시지를 보여주는 것이 좋은 UX입니다. 정리 김개발 씨는 세 개의 함수를 모두 작성하고 테스트해보았습니다.

"와, 이제 실제 앱을 만들 수 있을 것 같아요!" 이 예제 코드를 기반으로 삭제 기능이나 수정 기능을 추가해보세요. DELETE 메서드와 PATCH 메서드를 사용하면 됩니다.

직접 코드를 작성하면서 배우는 것이 가장 빠른 학습 방법입니다.

실전 팁

💡 - async/await를 사용할 때는 반드시 try-catch로 에러를 처리하세요.

  • API URL은 환경 변수로 관리하면 개발/운영 환경을 쉽게 전환할 수 있습니다.
  • 자주 사용하는 fetch 패턴은 별도의 유틸리티 함수로 만들어두면 편리합니다.

4. 실제 소스 코드 분석

김개발 씨는 기본 예제를 완성했지만, 실제 프로덕션 코드는 어떻게 생겼을지 궁금해졌습니다. 박시니어 씨는 자신이 만든 실제 프로젝트의 코드 일부를 보여주었습니다.

"실무에서는 이렇게 사용합니다."

실제 프로덕션 코드는 단순한 예제보다 훨씬 복잡합니다. 에러 처리, 로딩 상태 관리, 타임아웃 설정, 재시도 로직 등 다양한 장치가 포함됩니다.

마치 학교에서 배운 자동차 운전 이론과 실제 도로 주행의 차이처럼, 실무 코드는 여러 예외 상황을 고려해야 합니다.

다음 코드를 살펴봅시다.

// 실전 fetch 래퍼 함수
async function fetchWithTimeout(url, options = {}, timeout = 5000) {
  // 타임아웃 설정
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeout);

  try {
    const response = await fetch(url, {
      ...options,
      signal: controller.signal  // 타임아웃 시그널 연결
    });

    clearTimeout(timeoutId);  // 성공 시 타임아웃 취소

    // HTTP 에러 체크
    if (!response.ok) {
      throw new Error(`HTTP Error: ${response.status}`);
    }

    return await response.json();
  } catch (error) {
    if (error.name === 'AbortError') {
      throw new Error('요청 시간 초과');
    }
    throw error;
  }
}

// 사용 예시
async function loadUserData(userId) {
  try {
    const data = await fetchWithTimeout(
      `https://api.example.com/users/${userId}`,
      { headers: { 'Authorization': 'Bearer token' } },
      3000  // 3초 타임아웃
    );
    return data;
  } catch (error) {
    console.error('사용자 데이터 로딩 실패:', error.message);
    return null;
  }
}

김개발 씨는 박시니어 씨가 보여준 코드를 보고 놀랐습니다. 예제 코드보다 훨씬 복잡했기 때문입니다.

"왜 이렇게 복잡한가요?" 김개발 씨가 물었습니다. 박시니어 씨는 미소를 지으며 설명했습니다.

"실제 서비스에서는 수많은 예외 상황이 발생합니다. 네트워크가 느리거나, 서버가 응답하지 않거나, 에러를 반환할 수 있죠." 타임아웃이 필요한 이유 기본 fetch는 타임아웃 기능이 없습니다.

만약 서버가 응답하지 않으면 요청이 영원히 대기 상태에 머무를 수 있습니다. 쉽게 비유하자면, 전화를 걸었는데 상대방이 받지 않는 상황입니다.

무한정 기다릴 수는 없으므로 일정 시간이 지나면 끊고 "통화 연결 실패"라고 알려줘야 합니다. 웹 애플리케이션도 마찬가지입니다.

AbortController의 역할 AbortController는 진행 중인 fetch 요청을 취소할 수 있게 해주는 Web API입니다. controller를 생성하고 setTimeout으로 지정된 시간이 지나면 abort()를 호출합니다.

이렇게 하면 fetch 요청이 중단되고 AbortError가 발생합니다. 이 에러를 catch에서 잡아서 사용자에게 적절한 메시지를 보여줄 수 있습니다.

response.ok 체크의 중요성 많은 초보 개발자가 놓치는 부분이 바로 이것입니다. fetch는 404나 500 같은 HTTP 에러 상태 코드를 받아도 catch로 빠지지 않습니다.

네트워크 연결 자체는 성공했기 때문입니다. 따라서 response.ok 속성을 확인하여 HTTP 상태 코드가 200번대인지 검증해야 합니다.

response.ok가 false면 명시적으로 에러를 던져서 catch 블록에서 처리하도록 합니다. 이렇게 하지 않으면 서버 에러를 성공으로 착각하여 잘못된 데이터를 처리하게 됩니다.

래퍼 함수의 장점 fetchWithTimeout 같은 래퍼 함수를 만들어두면 코드 중복을 크게 줄일 수 있습니다. 타임아웃 설정, 에러 체크, 공통 헤더 추가 등의 로직을 한 곳에 모아두면 유지보수가 훨씬 쉬워집니다.

나중에 인증 토큰 갱신 로직을 추가하거나 로깅을 추가할 때도 이 함수 하나만 수정하면 프로젝트 전체에 적용됩니다. 실무에서의 활용 대부분의 프로덕션 프로젝트는 이런 래퍼 함수를 가지고 있습니다.

예를 들어 axios 라이브러리가 인기 있는 이유도 바로 이런 기능들을 기본으로 제공하기 때문입니다. 하지만 fetch 역시 적절한 래퍼 함수만 만들어두면 충분히 강력합니다.

번들 크기를 줄이고 싶은 프로젝트에서는 외부 라이브러리 대신 이런 커스텀 래퍼를 선호합니다. 에러 메시지의 중요성 catch 블록에서 에러를 처리할 때는 사용자에게 유용한 정보를 제공해야 합니다.

"에러가 발생했습니다"보다는 "네트워크 연결을 확인해주세요" 또는 "서버가 응답하지 않습니다. 잠시 후 다시 시도해주세요" 같은 구체적인 메시지가 훨씬 좋습니다.

개발자 콘솔에는 상세한 에러 정보를, 사용자에게는 이해하기 쉬운 메시지를 보여주는 것이 베스트 프랙티스입니다. 정리 김개발 씨는 실전 코드의 중요성을 깨달았습니다.

"단순히 작동하는 것과 안정적으로 작동하는 것은 다른 문제네요." 여러분도 프로젝트에 fetch를 적용할 때 이런 패턴들을 참고하세요. 처음에는 복잡해 보이지만, 한 번 만들어두면 계속 재사용할 수 있어 결국 시간을 절약하게 됩니다.

실전 팁

💡 - 타임아웃은 API 특성에 따라 적절히 설정하세요. 이미지 업로드는 길게, 간단한 조회는 짧게.

  • response.ok 체크를 습관화하세요. 이것만으로도 많은 버그를 예방할 수 있습니다.
  • 래퍼 함수는 프로젝트 초기에 만들어두는 것이 좋습니다. 나중에 수정하기 어려워집니다.

5. 자주 하는 실수와 해결법

며칠 후, 김개발 씨는 fetch를 사용하다 이상한 버그를 만났습니다. 분명히 데이터를 받아왔는데 화면에 표시되지 않는 것입니다.

박시니어 씨가 코드를 보더니 웃으며 말했습니다. "아, 이거 초보자들이 정말 자주 하는 실수예요."

fetch를 처음 사용할 때 흔히 겪는 실수들이 있습니다. JSON 변환을 빠뜨리거나, async/await를 잘못 사용하거나, CORS 에러를 해결하지 못하는 경우가 대표적입니다.

마치 자전거를 처음 탈 때 넘어지는 것처럼, 이런 실수는 자연스러운 학습 과정입니다.

다음 코드를 살펴봅시다.

// 실수 1: response를 두 번 읽으려는 시도
async function wrongWay1() {
  const response = await fetch('https://api.example.com/data');
  const data1 = await response.json();  // 첫 번째 읽기 - 성공
  const data2 = await response.json();  // 두 번째 읽기 - 에러!
}

// 올바른 방법: 한 번만 읽고 변수에 저장
async function correctWay1() {
  const response = await fetch('https://api.example.com/data');
  const data = await response.json();
  // data를 여러 번 사용
  console.log(data);
  processData(data);
}

// 실수 2: await 없이 fetch 사용
function wrongWay2() {
  const data = fetch('https://api.example.com/data').json();  // Promise 객체!
  console.log(data);  // [object Promise] 출력됨
}

// 올바른 방법: await 사용
async function correctWay2() {
  const response = await fetch('https://api.example.com/data');
  const data = await response.json();
  console.log(data);  // 실제 데이터 출력
}

// 실수 3: 에러 처리 누락
async function wrongWay3() {
  const response = await fetch('https://api.example.com/data');
  const data = await response.json();  // 에러 발생 시 앱 중단!
}

// 올바른 방법: try-catch 사용
async function correctWay3() {
  try {
    const response = await fetch('https://api.example.com/data');
    if (!response.ok) throw new Error('HTTP Error');
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('데이터 로딩 실패:', error);
    return null;  // 기본값 반환
  }
}

김개발 씨의 코드를 본 박시니어 씨는 즉시 문제를 발견했습니다. "여기 response.json()을 두 번 호출했네요." 김개발 씨는 고개를 갸우뚱했습니다.

"그게 문제인가요?" 박시니어 씨는 차근차근 설명을 시작했습니다. 실수 1: Response를 두 번 읽으려는 시도 fetch로 받은 response 객체는 스트림입니다.

한 번 읽으면 소모되어 다시 읽을 수 없습니다. 쉽게 비유하자면, 일회용 티켓과 같습니다.

한 번 개표하면 다시 사용할 수 없는 것처럼, response.json()을 한 번 호출하면 스트림이 소진됩니다. 두 번째 호출하면 "이미 읽었습니다"라는 에러가 발생합니다.

따라서 json()을 호출한 결과를 변수에 저장해두고, 그 변수를 여러 번 사용해야 합니다. 이것만 기억해도 많은 버그를 예방할 수 있습니다.

실수 2: await를 빠뜨리는 경우 async/await를 처음 배울 때 가장 흔한 실수입니다. fetch는 Promise를 반환합니다.

Promise는 "미래에 값을 줄게요"라는 약속이지, 실제 값이 아닙니다. await 키워드 없이 사용하면 Promise 객체 자체를 받게 되어 데이터를 사용할 수 없습니다.

콘솔에 [object Promise]가 출력되는 것을 보고 당황한 경험이 있다면 바로 이 실수를 한 것입니다. response.json()도 Promise를 반환하므로 반드시 await를 붙여야 합니다.

실수 3: 에러 처리를 생략하는 경우 "테스트에서는 잘 작동하니까 괜찮겠지"라고 생각하는 순간이 위험합니다. 실제 서비스에서는 네트워크가 불안정하거나, 서버가 일시적으로 다운되거나, 예상치 못한 에러가 발생할 수 있습니다.

이럴 때 try-catch가 없으면 애플리케이션 전체가 멈춰버릴 수 있습니다. 사용자는 아무런 피드백도 받지 못한 채 멈춘 화면만 보게 됩니다.

최악의 사용자 경험입니다. 항상 에러를 처리하고 적절한 메시지를 보여주는 습관을 들여야 합니다.

CORS 에러의 미스터리 김개발 씨는 다른 질문도 했습니다. "선배님, CORS 에러는 왜 자꾸 나는 거예요?" CORS(Cross-Origin Resource Sharing)는 브라우저의 보안 정책입니다.

다른 도메인의 리소스에 접근할 때 서버의 허락이 필요합니다. 쉽게 비유하자면, 다른 회사 건물에 들어갈 때 출입증이 필요한 것과 같습니다.

localhost에서 개발하다가 외부 API를 호출하면 도메인이 다르므로 CORS 에러가 발생할 수 있습니다. 해결 방법은 두 가지입니다.

백엔드 개발자에게 요청하여 서버에서 CORS 헤더를 추가하거나, 개발 환경에서 프록시를 설정하는 것입니다. Create React App 같은 도구는 proxy 설정을 package.json에 추가하면 자동으로 처리해줍니다.

JSON 파싱 에러 또 다른 흔한 에러는 JSON 파싱 실패입니다. 서버가 JSON이 아닌 HTML이나 텍스트를 반환하는데 response.json()을 호출하면 에러가 발생합니다.

response.ok를 체크하고, Content-Type 헤더를 확인하여 실제로 JSON 데이터인지 검증하는 것이 안전합니다. 정리 김개발 씨는 자신의 코드를 수정했고, 드디어 정상적으로 작동했습니다.

"이런 실수를 미리 알았다면 시간을 많이 절약했을 텐데요." 실수는 학습 과정의 일부입니다. 하지만 흔한 실수를 미리 알고 있으면 같은 함정에 빠지지 않을 수 있습니다.

여러분도 이 패턴들을 기억해두세요.

실전 팁

💡 - response 객체는 한 번만 읽을 수 있다는 점을 항상 기억하세요.

  • await를 빠뜨렸는지 의심될 때는 console.log로 타입을 확인해보세요.
  • 에러 처리는 선택이 아니라 필수입니다. 모든 fetch 호출에 try-catch를 추가하세요.

6. 실전 활용 팁

김개발 씨는 이제 fetch를 능숙하게 사용할 수 있게 되었습니다. 박시니어 씨는 마지막으로 실전에서 유용한 팁들을 공유했습니다.

"이제 초보는 벗어났으니, 중급자로 가는 비법을 알려드릴게요."

실전 프로젝트에서는 기본 사용법 이상의 테크닉이 필요합니다. 병렬 요청 처리, 요청 취소, 재시도 로직, 캐싱 전략 등을 활용하면 더 빠르고 안정적인 애플리케이션을 만들 수 있습니다.

마치 요리의 기본을 배운 후 셰프의 특별한 레시피를 배우는 것과 같습니다.

다음 코드를 살펴봅시다.

// 팁 1: 여러 요청을 병렬로 처리
async function fetchMultipleResources() {
  const [users, posts, comments] = await Promise.all([
    fetch('https://api.example.com/users').then(r => r.json()),
    fetch('https://api.example.com/posts').then(r => r.json()),
    fetch('https://api.example.com/comments').then(r => r.json())
  ]);
  return { users, posts, comments };
}

// 팁 2: 요청 재시도 로직
async function fetchWithRetry(url, options = {}, retries = 3) {
  for (let i = 0; i < retries; i++) {
    try {
      const response = await fetch(url, options);
      if (!response.ok) throw new Error('HTTP Error');
      return await response.json();
    } catch (error) {
      if (i === retries - 1) throw error;  // 마지막 시도에서는 에러 던짐
      await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));  // 지수 백오프
    }
  }
}

// 팁 3: 간단한 캐시 구현
const cache = new Map();

async function fetchWithCache(url, maxAge = 60000) {
  const cached = cache.get(url);
  if (cached && Date.now() - cached.timestamp < maxAge) {
    return cached.data;  // 캐시된 데이터 반환
  }

  const response = await fetch(url);
  const data = await response.json();
  cache.set(url, { data, timestamp: Date.now() });
  return data;
}

// 팁 4: 진행 상황 추적 (파일 업로드)
async function uploadWithProgress(file, onProgress) {
  const formData = new FormData();
  formData.append('file', file);

  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.upload.addEventListener('progress', (e) => {
      if (e.lengthComputable) {
        const percent = (e.loaded / e.total) * 100;
        onProgress(percent);
      }
    });

    xhr.addEventListener('load', () => resolve(xhr.response));
    xhr.addEventListener('error', reject);
    xhr.open('POST', 'https://api.example.com/upload');
    xhr.send(formData);
  });
}

김개발 씨는 지난 몇 주간 fetch를 사용하면서 실력이 많이 늘었습니다. 박시니어 씨는 이제 더 고급 테크닉을 가르쳐줄 때가 되었다고 판단했습니다.

"기본은 마스터했으니, 이제 성능과 사용자 경험을 개선하는 방법을 배워봅시다." 박시니어 씨의 특강이 시작되었습니다. 병렬 요청으로 속도 향상 여러 개의 독립적인 API 요청이 필요할 때, 하나씩 순차적으로 호출하면 시간이 오래 걸립니다.

Promise.all을 사용하면 모든 요청을 동시에 보내고 모두 완료될 때까지 기다릴 수 있습니다. 쉽게 비유하자면, 여러 친구에게 동시에 전화를 걸어 모두의 답변을 한꺼번에 받는 것과 같습니다.

예를 들어 대시보드 페이지에서 사용자 정보, 최근 게시물, 댓글을 모두 불러와야 한다면 Promise.all을 사용하세요. 각각 1초씩 걸린다면 순차 실행은 3초, 병렬 실행은 1초면 충분합니다.

3배의 성능 향상입니다. 재시도 로직으로 안정성 향상 네트워크는 항상 불안정합니다.

특히 모바일 환경에서는 순간적인 연결 끊김이 자주 발생합니다. 재시도 로직을 추가하면 일시적인 에러를 자동으로 복구할 수 있습니다.

코드를 보면 for 루프로 지정된 횟수만큼 재시도하고, 각 시도 사이에 대기 시간을 둡니다. 지수 백오프(exponential backoff)를 사용한 점이 포인트입니다.

첫 번째 재시도는 1초 후, 두 번째는 2초 후, 세 번째는 3초 후에 실행됩니다. 이렇게 하면 서버에 부담을 주지 않으면서도 효과적으로 재시도할 수 있습니다.

캐싱으로 불필요한 요청 줄이기 같은 데이터를 여러 번 요청하는 것은 비효율적입니다. 캐싱을 구현하면 한 번 받아온 데이터를 메모리에 저장해두고 재사용할 수 있습니다.

Map 객체를 사용하여 URL을 키로, 데이터와 타임스탬프를 값으로 저장합니다. maxAge 파라미터로 캐시 유효 시간을 설정할 수 있습니다.

예를 들어 사용자 프로필 정보는 1분 정도 캐싱해도 괜찮지만, 실시간 주식 시세는 캐싱하면 안 됩니다. 데이터의 특성에 맞게 적절한 값을 설정하세요.

파일 업로드 진행률 표시 큰 파일을 업로드할 때 사용자는 진행 상황을 알고 싶어 합니다. 안타깝게도 fetch API는 업로드 진행률을 추적하는 기능이 없습니다.

이럴 때는 XMLHttpRequest를 사용해야 합니다. 구식 API지만 progress 이벤트를 지원합니다.

코드를 보면 xhr.upload에 progress 이벤트 리스너를 추가합니다. 이벤트 객체의 loaded와 total 속성으로 현재 업로드된 바이트와 전체 크기를 알 수 있어 퍼센트를 계산할 수 있습니다.

이 값으로 프로그레스 바를 업데이트하면 사용자에게 훌륭한 피드백을 제공할 수 있습니다. 실무에서의 조합 이 테크닉들은 따로 사용해도 좋지만, 함께 조합하면 더 강력합니다.

예를 들어 전자상거래 사이트의 상품 상세 페이지를 생각해봅시다. 상품 정보, 리뷰, 연관 상품을 Promise.all로 병렬 로딩하고, 상품 정보는 캐싱하고, 네트워크가 불안정할 때를 대비해 재시도 로직을 추가하면 매우 안정적이고 빠른 사용자 경험을 제공할 수 있습니다.

주의할 점 하지만 과도한 최적화는 독이 될 수 있습니다. 모든 요청에 캐싱을 적용하면 오래된 데이터를 보여줄 수 있고, 너무 많은 재시도는 서버에 부담을 줄 수 있습니다.

항상 실제 필요에 맞게 적절히 사용해야 합니다. 성능 측정 도구로 실제 개선 효과를 확인하는 것도 좋은 습관입니다.

정리 김개발 씨는 이제 fetch의 초급자에서 중급자로 성장했습니다. "이 정도면 실무 프로젝트에서도 자신 있게 사용할 수 있겠어요!" 박시니어 씨는 만족스러운 표정으로 말했습니다.

"좋아요. 이제 직접 프로젝트에 적용하면서 더 많은 경험을 쌓아보세요." 여러분도 오늘 배운 fetch의 모든 것을 실전에 활용해보세요.

처음에는 어렵게 느껴지겠지만, 연습하다 보면 어느새 능숙하게 사용하고 있는 자신을 발견하게 될 것입니다.

실전 팁

💡 - Promise.all 사용 시 하나라도 실패하면 전체가 실패합니다. Promise.allSettled를 사용하면 개별 성공/실패를 처리할 수 있습니다.

  • 캐시 구현 시 메모리 누수에 주의하세요. 주기적으로 오래된 항목을 정리하는 로직을 추가하는 것이 좋습니다.
  • 재시도 횟수와 대기 시간은 API 서버의 정책에 맞춰 조정하세요.

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#JavaScript#fetch#API#HTTP#AsyncAwait#React,State Management,CLI

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.