🌐 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 같은 라이브러리를 도입하는 게 좋습니다.