Skip to main content
Summary

웹페이지가 바깥 데이터를 만나면 달라지는 것

지금까지 우리는 자바스크립트로 화면의 글자를 바꾸고, 버튼 클릭에 반응하고, 브라우저 저장소에 간단한 값을 남겨 보았습니다. 이제 한 단계 더 나아가 보겠습니다. 웹페이지 밖에 있는 데이터를 가져오는 일입니다.

예를 들어 이런 기능을 떠올릴 수 있습니다.

  • 오늘의 날씨 보여 주기

  • 환율 정보 가져오기

  • 영화 목록 불러오기

  • 쇼핑몰 상품 목록 표시하기

  • 블로그 글 목록 최신순으로 보여 주기

이런 데이터는 보통 내 HTML 파일 안에 직접 들어 있지 않습니다. 다른 컴퓨터, 즉 서버에 있습니다. 서버는 데이터를 보관하고 있다가 브라우저가 요청하면 응답을 보내 줍니다.

자바스크립트에서 이 요청을 보낼 때 자주 사용하는 기능이 fetch()입니다.

인터넷에서 데이터를 가져오는 fetch와 JSONAI 생성 이미지

JSON은 데이터를 주고받기 위한 문서다

서버가 브라우저에게 데이터를 보낼 때는 사람이 읽기 좋고, 프로그램도 다루기 쉬운 형식이 필요합니다. 이때 많이 쓰는 형식이 JSON입니다.

JSON은 "자바스크립트 객체처럼 생긴 데이터 교환용 문서"라고 생각하면 됩니다. 객체와 비슷하게 이름과 값이 짝을 이룹니다.

{
  "name": "민지",
  "age": 28,
  "isMember": true
}

자바스크립트 객체와 생김새가 비슷하지만, JSON은 더 엄격한 규칙을 가집니다.

  • 속성 이름은 큰따옴표로 감쌉니다. 예: "name"

  • 문자열도 큰따옴표로 감쌉니다. 예: "민지"

  • 값으로 문자열, 숫자, 불리언, 배열, 객체, null 등을 넣을 수 있습니다.

  • 함수는 넣지 않습니다. JSON은 "실행할 코드"가 아니라 "전달할 데이터"입니다.

실제 데이터는 배열과 객체가 섞인 모양이 많습니다.

{
  "city": "Seoul",
  "weather": {
    "temperature": 27,
    "description": "맑음"
  },
  "weekly": [
    { "day": "월", "temperature": 26 },
    { "day": "화", "temperature": 28 },
    { "day": "수", "temperature": 25 }
  ]
}

이 구조를 자바스크립트 객체로 받았다고 생각해 봅시다.

console.log(data.city);
console.log(data.weather.temperature);
console.log(data.weekly[0].day);

읽는 순서는 바깥에서 안쪽으로 들어갑니다.

  • data.city : 도시 이름

  • data.weather.temperature : weather 객체 안의 temperature

  • data.weekly[0].day : weekly 배열의 첫 번째 객체 안의 day

배열은 대괄호와 인덱스로 접근하고, 객체는 점 표기법으로 속성을 꺼냅니다. 목록과 묶음 정보를 다룰 때 배운 배열과 객체를 인터넷 데이터에 적용하는 것입니다.

fetch는 서버에 주문을 넣는 일이다

fetch()는 브라우저가 서버에 데이터를 요청할 때 사용하는 기능입니다. 식당에 비유하면 이해하기 쉽습니다.

  • 손님이 주문한다 → 브라우저가 서버에 요청한다

  • 주방이 음식을 준비한다 → 서버가 데이터를 준비한다

  • 직원이 음식을 가져온다 → 서버가 응답을 보낸다

  • 손님이 음식을 확인하고 먹는다 → 자바스크립트가 응답을 읽고 화면에 표시한다

기본 모양은 다음과 같습니다.

fetch("https://example.com/data");

하지만 이 한 줄만으로는 데이터를 바로 사용할 수 없습니다. fetch()는 "요청을 보냈고, 응답이 오면 알려 주겠다"는 약속을 먼저 돌려줍니다. 실제 응답은 시간이 걸릴 수 있습니다. 인터넷이 느릴 수도 있고, 서버가 잠시 바쁠 수도 있기 때문입니다.

그래서 fetch()는 보통 async와 await와 함께 씁니다.

async와 await는 기다리라는 표시다

async와 await를 처음 보면 낯설 수 있습니다. 지금은 복잡한 내부 원리보다 사용 패턴을 먼저 익히면 됩니다.

  • async : 이 함수 안에서는 기다리는 코드를 쓸 수 있다고 표시합니다.

  • await : 이 작업이 끝날 때까지 기다린 뒤 다음 줄로 넘어가라고 표시합니다.

기본 패턴은 다음과 같습니다.

async function loadData() {
  const response = await fetch("데이터 주소");
  const data = await response.json();

  console.log(data);
}

loadData();

여기서 중요한 줄은 두 줄입니다.

const response = await fetch("데이터 주소");

서버에 요청을 보내고 응답을 기다립니다. 이때 받은 response는 아직 우리가 바로 쓰고 싶은 데이터 자체가 아닙니다. 택배 상자로 비유하면, 상자를 받은 상태입니다.

const data = await response.json();

응답 안에 들어 있는 JSON 내용을 자바스크립트가 다룰 수 있는 값으로 꺼냅니다. 택배 상자를 열어 실제 물건을 꺼내는 단계입니다.

response.json()도 시간이 걸릴 수 있어서 await를 붙입니다. MDN 문서에서도 fetch()가 응답 객체를 돌려주고, JSON 본문을 읽으려면 response.json() 같은 메서드를 따로 사용한다고 설명합니다.1

콘솔에서 첫 데이터 가져오기

브라우저 개발자 도구의 Console 탭에서 아래 코드를 실행해 봅시다. 연습용 공개 API에서 사용자 한 명의 정보를 가져옵니다.

async function loadUser() {
  const response = await fetch("https://jsonplaceholder.typicode.com/users/1");
  const user = await response.json();

  console.log(user);
  console.log(user.name);
  console.log(user.email);
}

loadUser();

실행하면 객체 하나가 출력됩니다. 그 안에는 이름, 이메일, 주소 같은 정보가 들어 있습니다.

조금 더 안쪽 정보도 꺼내 볼 수 있습니다. 아래 줄을 loadUser 함수 안에 추가해 보세요.

console.log(user.address.city);

전체 코드는 이런 모양이 됩니다.

async function loadUser() {
  const response = await fetch("https://jsonplaceholder.typicode.com/users/1");
  const user = await response.json();

  console.log(user);
  console.log(user.name);
  console.log(user.email);
  console.log(user.address.city);
}

loadUser();

user 객체 안에 address 객체가 있고, 그 안에 city가 있는 구조입니다.

처음에는 전체를 한 번 출력한 뒤, 필요한 속성을 하나씩 꺼내는 습관이 좋습니다.

console.log(user);

이 한 줄은 데이터를 탐색하는 지도와 같습니다. 어떤 속성이 있는지 먼저 확인하면 실수를 줄일 수 있습니다.

가져온 데이터를 화면에 표시하기

이제 콘솔이 아니라 HTML 화면에 표시해 보겠습니다. 아래 내용을 fetch-practice.html 파일로 저장하고 브라우저에서 열어 보세요.

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <title>fetch 연습</title>
</head>
<body>
  <h2>사용자 정보</h2>
  <button id="loadButton">정보 불러오기</button>
  <div id="result">아직 불러온 정보가 없습니다.</div>

  <script>
    const loadButton = document.querySelector("#loadButton");
    const result = document.querySelector("#result");

    async function loadUser() {
      const response = await fetch("https://jsonplaceholder.typicode.com/users/1");
      const user = await response.json();

      result.innerHTML = `
        <p>이름: ${user.name}</p>
        <p>이메일: ${user.email}</p>
        <p>도시: ${user.address.city}</p>
      `;
    }

    loadButton.addEventListener("click", loadUser);
  </script>
</body>
</html>

흐름을 나누어 보면 어렵지 않습니다.

  1. 버튼과 결과 영역을 찾습니다.

  2. 버튼을 클릭하면 loadUser 함수가 실행됩니다.

  3. fetch()로 데이터를 요청합니다.

  4. response.json()으로 JSON을 꺼냅니다.

  5. 필요한 값을 HTML 문자열로 만들어 화면에 넣습니다.

여기서 웹페이지를 직접 바꾸는 DOM 조작과 클릭에 반응하는 이벤트가 다시 등장합니다. 인터넷 데이터는 혼자 빛나지 않습니다. 가져온 뒤 화면에 잘 보여 줄 때 웹페이지의 기능이 됩니다.

오류가 생겼을 때 친절하게 알려 주기

인터넷 요청은 실패할 수 있습니다. 주소를 잘못 적을 수도 있고, 네트워크가 끊길 수도 있고, 서버가 오류를 보낼 수도 있습니다. 그래서 try...catch를 함께 쓰는 습관이 중요합니다.

try...catch는 "일단 시도해 보고, 문제가 생기면 이쪽에서 처리하라"는 문법입니다.

async function loadUser() {
  try {
    const response = await fetch("https://jsonplaceholder.typicode.com/users/1");

    if (!response.ok) {
      throw new Error(`요청 실패: ${response.status}`);
    }

    const user = await response.json();

    result.innerHTML = `
      <p>이름: ${user.name}</p>
      <p>이메일: ${user.email}</p>
    `;
  } catch (error) {
    result.textContent = "정보를 불러오지 못했습니다. 잠시 후 다시 시도해 주세요.";
    console.log(error.message);
  }
}

눈여겨볼 부분은 이것입니다.

if (!response.ok) {
  throw new Error(`요청 실패: ${response.status}`);
}

fetch()는 주소가 없는 페이지처럼 서버가 404 응답을 보내도 자동으로 catch로 가지는 않습니다. 응답 자체는 도착했기 때문입니다. 그래서 response.ok를 확인해 성공 응답인지 직접 검사하는 편이 안전합니다.1

  • response.ok : 요청이 성공 범위인지 알려 주는 값입니다. 보통 200번대 응답이면 true입니다.

  • response.status : 200, 404, 500 같은 상태 번호입니다.

  • throw new Error(...) : 일부러 오류를 만들어 catch에서 처리하게 합니다.

사용자에게는 짧고 친절한 메시지를 보여 주고, 개발자는 콘솔에서 자세한 내용을 확인하면 됩니다.

GET은 조회하기, POST는 보내기

API 문서를 읽다 보면 GET, POST 같은 단어를 자주 만납니다. 처음에는 두 가지만 기억해도 충분합니다.

GET은 데이터를 조회할 때 사용합니다.

const response = await fetch("https://example.com/products");

fetch()는 기본적으로 GET 요청을 보냅니다. 상품 목록 보기, 게시글 읽기, 날씨 정보 가져오기처럼 "주세요"에 가깝습니다.

POST는 데이터를 보낼 때 사용합니다.

const newPost = {
  title: "첫 글",
  body: "내용입니다.",
  userId: 1
};

const response = await fetch("https://example.com/posts", {
  method: "POST",
  headers: {
    "Content-Type": "application/json"
  },
  body: JSON.stringify(newPost)
});

여기서 JSON.stringify()는 자바스크립트 객체를 JSON 문자열로 바꾸는 기능입니다. 서버에 JSON 형식으로 보낼 때는 "자바스크립트 객체 그대로"가 아니라, 전송 가능한 문자열 형태로 바꿔야 합니다.

정리하면 이렇습니다.

  • 받을 때: response.json()으로 JSON을 자바스크립트 값처럼 꺼낸다.

  • 보낼 때: JSON.stringify()로 자바스크립트 객체를 JSON 문자열로 바꾼다.

이 장에서는 POST를 깊게 다루지 않습니다. 지금 목표는 API 문서를 봤을 때 "GET은 조회, POST는 보내기" 정도를 알아보는 것입니다.

fetch 코드를 읽는 순서

앞으로 데이터를 가져오는 코드를 보면 아래 순서로 읽어 보세요.

async function loadSomething() {
  try {
    const response = await fetch("주소");

    if (!response.ok) {
      throw new Error(`요청 실패: ${response.status}`);
    }

    const data = await response.json();

    console.log(data);
  } catch (error) {
    console.log(error.message);
  }
}

읽는 순서는 이렇습니다.

  1. async function : 기다리는 작업이 있는 함수다.

  2. await fetch("주소") : 서버에 요청하고 응답을 기다린다.

  3. response.ok : 응답이 성공인지 확인한다.

  4. await response.json() : JSON 내용을 꺼낸다.

  5. data 사용 : 콘솔에 찍거나 화면에 표시한다.

  6. catch : 실패했을 때 처리한다.

이 패턴은 날씨, 환율, 영화, 상품, 게시글 목록에도 거의 비슷하게 적용됩니다. 주소와 데이터 구조만 달라질 뿐입니다.

작은 실습: 사용자 목록 보여 주기

마지막으로 여러 명의 사용자 목록을 화면에 표시해 봅시다.

<button id="loadUsersButton">사용자 목록 불러오기</button>
<ul id="userList"></ul>

<script>
  const loadUsersButton = document.querySelector("#loadUsersButton");
  const userList = document.querySelector("#userList");

  async function loadUsers() {
    try {
      userList.textContent = "불러오는 중입니다...";

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

      if (!response.ok) {
        throw new Error(`요청 실패: ${response.status}`);
      }

      const users = await response.json();

      userList.innerHTML = "";

      users.forEach(function(user) {
        const li = document.createElement("li");
        li.textContent = `${user.name} - ${user.email}`;
        userList.appendChild(li);
      });
    } catch (error) {
      userList.textContent = "사용자 목록을 불러오지 못했습니다.";
      console.log(error.message);
    }
  }

  loadUsersButton.addEventListener("click", loadUsers);
</script>

이 예제에는 지금까지 배운 내용이 많이 들어 있습니다.

  • 버튼 클릭 이벤트

  • fetch()로 데이터 요청

  • async와 await로 기다리기

  • JSON을 배열로 받아오기

  • forEach()로 배열 반복하기

  • createElement()와 appendChild()로 목록 만들기

  • try...catch로 실패 처리하기

처음부터 모두 외울 필요는 없습니다. 중요한 것은 흐름입니다.

요청한다 → 기다린다 → JSON을 꺼낸다 → 화면에 표시한다 → 실패하면 안내한다.

이 흐름을 이해하면, 웹페이지는 더 이상 고정된 문서가 아닙니다. 바깥의 데이터를 가져와 지금 필요한 정보를 보여 주는 도구가 됩니다.

참고

1Using the Fetch API - MDN

책 목록

비개발자를 위한 JavaScript: 설치 없이 시작해 작은 프로젝트까지

전체 목차 보기

이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.

틸노트는 사용자가 AI를 활용해 노트를 쉽게 작성하거나 편집할 수 있는 서비스입니다.

AI 스퀘어 단톡방에 함께해요

AI 활용과 틸노트 사용 팁을 나누는 오픈채팅방입니다. 입장 비밀번호는 tilnote입니다.

참여하기