🌐 fetch API & 비동기 JavaScript

fetch API
완전 정복

날씨, 뉴스, 가격 정보를 외부에서 가져오고 싶다면 fetch가 필요해요. async/await, 에러 처리, 로딩 상태까지 — 실제 공개 API로 바로 체험해봐요.

🌐 fetch + async/await ⚡ 실제 API 호출 데모 🔐 에러 처리 포함 📋 복사 가능한 코드
① Concept

동기 vs 비동기 — 왜 async/await인가?

JavaScript는 기본적으로 한 줄씩 순서대로 실행돼요(동기). 그런데 서버에서 데이터를 가져오는 건 시간이 걸리기 때문에 기다리는 동안 다른 작업을 할 수 있어야 해요(비동기). async/await는 비동기 코드를 동기 코드처럼 읽기 쉽게 써주는 문법이에요.

💡

fetch 흐름: 브라우저 → API 서버에 요청 → 서버가 JSON 응답 → response.json()으로 파싱 → 데이터 사용. 이 모든 단계가 비동기이기 때문에 await으로 기다려야 해요.

📡

fetch(url)

URL에 HTTP 요청을 보내요. Promise를 반환하므로 await으로 결과를 기다려야 해요. 기본값은 GET 요청이고, 두 번째 인자로 옵션을 넣으면 POST 등 다른 메서드도 사용할 수 있어요.

📦

response.json()

서버 응답 본문을 JSON으로 파싱해요. 이것도 비동기라 await이 필요해요. response.text()는 텍스트, response.blob()은 이미지/파일을 받을 때 사용해요.

⚡

async / await

함수 앞에 async를 붙이면 그 함수 안에서 await을 쓸 수 있어요. await은 Promise가 완료될 때까지 코드 실행을 잠시 멈춰요. 동기 코드처럼 읽히지만 실제론 비동기예요.

🚨

try / catch

네트워크 에러, 타임아웃, API 서버 오류 등 예외를 잡아요. response.ok가 false면 HTTP 4xx/5xx 에러예요. fetch 자체는 에러를 던지지 않기 때문에 if (!response.ok) 체크가 꼭 필요해요.

방식읽기 난이도에러 처리권장도
콜백 (구식) 콜백 지옥 번거로움 지양
.then().catch() (Promise) 중간 가능 보통
async / await (현재 표준) 동기 코드처럼 try/catch ✅ 권장
② Patterns

실무에서 쓰는 4가지 패턴

이 패턴들을 조합하면 대부분의 API 연동 시나리오를 처리할 수 있어요.

⬇️

GET — 데이터 가져오기

날씨, 게시글 목록, 환율 등 서버에서 데이터를 읽을 때 사용해요. 기본 fetch는 자동으로 GET 요청이에요. URL에 쿼리스트링(?key=value)으로 파라미터를 전달해요.

⬆️

POST — 데이터 보내기

회원가입, 댓글 작성, 주문처럼 서버에 새 데이터를 생성할 때 사용해요. method: 'POST', headers, body를 옵션으로 넣어요. body는 JSON.stringify()로 직렬화해야 해요.

⏳

로딩 상태 표시

요청 중에는 스피너나 "로딩 중..." 메시지를 보여주고, 완료되면 숨겨요. 버튼을 비활성화해서 중복 요청을 막는 것도 중요해요. UX에서 가장 많이 놓치는 부분이에요.

🚨

에러 처리

네트워크 단절, API 키 오류(401), 찾을 수 없음(404), 서버 오류(500) 등을 각각 처리해요. 사용자에게 친절한 메시지를 보여주는 게 중요해요. 에러를 그냥 콘솔에만 출력하면 사용자는 왜 안 되는지 모릅니다.

③ Code

복사해서 바로 쓰는 코드

가장 많이 쓰이는 패턴을 템플릿으로 정리했어요. 복사해서 AI에게 "우리 프로젝트에 맞게 수정해줘"라고 하면 돼요.

1 — 기본 GET 요청 (async/await)

가장 많이 쓰이는 표준 패턴이에요. 이 구조를 외워두세요.

script.js
// async 함수 안에서만 await 사용 가능
async function getData(url) {
  try {
    // 1. 요청 보내기
    const response = await fetch(url);

    // 2. HTTP 에러 체크 (fetch는 4xx/5xx에서 에러 안 던짐!)
    if (!response.ok) {
      throw new Error(`HTTP 에러: ${response.status}`);
    }

    // 3. JSON 파싱
    const data = await response.json();

    // 4. 데이터 활용
    console.log(data);
    return data;

  } catch (err) {
    // 5. 에러 처리
    console.error('데이터 로드 실패:', err.message);
    throw err; // 호출한 곳으로 에러 전달
  }
}

// 호출
getData('https://api.example.com/posts');

2 — 로딩 상태와 함께 UI 업데이트

실제 서비스에서 꼭 필요한 로딩·성공·에러 세 가지 상태 처리 패턴이에요.

script.js
async function loadPosts() {
  const btn     = document.getElementById('load-btn');
  const result  = document.getElementById('result');
  const status  = document.getElementById('status');

  // 로딩 시작
  btn.disabled   = true;
  status.textContent = '⏳ 로딩 중...';
  result.innerHTML   = '';

  try {
    const res  = await fetch('https://jsonplaceholder.typicode.com/posts?_limit=5');
    if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
    const data = await res.json();

    // 성공: 카드 렌더링
    status.textContent = `✅ 게시글 ${data.length}개 로드 완료`;
    result.innerHTML = data.map(post => `
      <div class="post-card">
        <strong>${post.title}</strong>
        <p>${post.body.substring(0, 80)}...</p>
      </div>
    `).join('');

  } catch (err) {
    // 에러: 사용자에게 표시
    status.textContent = `❌ 오류: ${err.message}`;
  } finally {
    btn.disabled = false; // 항상 버튼 복구
  }
}

3 — POST 요청 (데이터 보내기)

폼 제출, 댓글 저장, 로그인 등 서버로 데이터를 전송할 때 쓰는 패턴이에요.

script.js
async function postData(url, body) {
  const response = await fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(body), // 객체 → JSON 문자열 변환
  });

  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  return response.json();
}

// 사용 예시
postData('https://jsonplaceholder.typicode.com/posts', {
  title: '새 게시글',
  body: '내용입니다.',
  userId: 1,
}).then(data => {
  console.log('저장 완료:', data);
});
⚠️

CORS 에러가 나요! 다른 도메인의 API를 브라우저에서 직접 호출하면 CORS(Cross-Origin Resource Sharing) 에러가 발생할 수 있어요. 서버 측에서 Access-Control-Allow-Origin 헤더를 허용해야 해요. 공개 API를 테스트할 때는 CORS를 허용한 API를 찾거나, Netlify Functions / Vercel Edge Functions 같은 중간 서버를 거쳐야 해요.

④ Demo

실제 API를 호출해보세요

아래 데모는 실제 공개 API(JSONPlaceholder)를 호출해요. 버튼을 눌러 데이터가 불러와지는 걸 직접 확인해보세요.

fetch-demo.js — 라이브 데모
← 위에서 엔드포인트를 선택하고 실행 버튼을 눌러보세요.
⑤ Prompt Tip

AI 프롬프트 팁

API 연동을 AI에게 맡길 때 구체적으로 요청할수록 완성도 높은 코드를 받을 수 있어요.

✦ 바이브코더 프롬프트

아래 프롬프트를 Claude, ChatGPT, Cursor에 복사해서 바로 사용해보세요.

내 HTML 페이지에서 외부 API 데이터를 가져와서 카드로 표시하는 기능을 만들어줘.

API 정보:
- URL: https://api.example.com/products
- 메서드: GET
- 응답 형태: JSON 배열, 각 항목에 name, price, image 필드 있음

요구사항:
- async/await 패턴 사용
- 로딩 중 스피너 표시, 완료 후 숨기기
- HTTP 에러(4xx, 5xx)와 네트워크 에러 모두 처리
- 에러 시 사용자에게 친절한 메시지 표시
- 각 제품을 카드 형태로 렌더링 (이름, 가격, 이미지)
- 버튼 클릭 시 중복 요청 방지

순수 HTML + Vanilla JS로 구현해줘.
💬

팁: 사용할 API의 실제 응답 JSON을 복사해서 "응답 예시는 이렇게 생겼어:"라고 함께 붙여주면 AI가 정확한 필드명으로 코드를 작성해줘요.

⑥ FAQ

자주 묻는 질문

fetch와 비동기를 처음 배울 때 맞닥뜨리는 질문들이에요.

404나 500 같은 HTTP 에러가 와도 fetch는 에러를 던지지 않아요. 네트워크 자체가 끊기거나 요청이 완전히 실패할 때만 에러를 던져요. 그래서 response.ok (status 200~299이면 true)를 직접 체크하고 수동으로 에러를 throw해야 해요.
const data = fetch(url)처럼 await 없이 쓰면 data는 실제 데이터가 아니라 Promise 객체예요. data.title을 출력하면 undefined가 나오는 게 흔한 실수예요. 비동기 작업이 있는 곳마다 await을 붙여야 해요.
절대 안 돼요! GitHub에 올리면 누구나 볼 수 있어요. 프론트엔드 코드에 있는 API 키는 개발자 도구로 바로 확인할 수 있어요. 민감한 API 키는 반드시 서버(Node.js, Vercel Functions, Netlify Functions 등) 환경변수에 넣고 프록시 역할을 해야 해요.
Promise.all([fetch(url1), fetch(url2)])을 사용하면 두 요청을 동시에 보내고 둘 다 완료될 때까지 기다려요. 순서대로 기다리는 것보다 훨씬 빠릅니다. 하나라도 실패하면 전체가 reject되므로 주의하세요. 개별 에러 처리가 필요하면 Promise.allSettled를 사용해요.
바이브 코딩 초반엔 외부 라이브러리 없이 쓸 수 있는 fetch를 추천해요. axios는 에러 처리가 더 직관적이고(HTTP 에러에서 자동 throw), 요청/응답 인터셉터, 자동 JSON 변환 등 편의 기능이 있어요. 프로젝트가 커지면 axios나 React Query 같은 라이브러리를 도입하는 게 좋습니다.