DOM을 움직일 수 있게 됐다면 이제 서버 데이터와 화면을 연결해야 합니다. fetch는 브라우저 내장 네트워크 함수, async/await는 Promise 흐름을 한 줄씩 읽히게 만드는 문법입니다.
이번 글에서 새로 나오는 용어
- async/await: Promise를 순서대로 읽히게 해 주는 키워드로,
await줄에서 잠시 멈췄다가 다음 줄을 실행합니다. - AbortController: 너무 오래 걸리는 네트워크 요청을 중간에 취소할 수 있게 만드는 도구입니다.
핵심 개념
- Promise: “나중에 결과를 알려주는 상자”입니다.
pending에서 시작해 성공(fulfilled) 또는 실패(rejected) 상태로 바뀝니다. - async/await: Promise를 조금 더 자연어처럼 읽게 도와주는 문법입니다.
await는 해당 Promise가 끝날 때까지 다음 줄 실행을 잠시 멈춥니다. - fetch: HTTP 요청을 보내는 브라우저 API입니다. 요청이 네트워크까지 도달하면 성공으로 간주하므로
response.ok를 반드시 확인해야 합니다. - 로딩/에러 상태: 사용자에게 지금 어떤 단계인지 알려 주는 문구나 UI입니다. 스켈레톤, 스피너, 텍스트 등을 조합합니다.
- AbortController: 느린 네트워크에서 요청을 취소하는 도구입니다.
signal을 전달하면 타임아웃이나 버튼 클릭으로 요청을 멈출 수 있습니다.
코드로 확인하기
async function loadPost(id) {
const response = await fetch(`/api/posts/${id}`);
if (!response.ok) {
throw new Error("게시글을 불러오지 못했습니다");
}
return response.json();
}
loadPost(1)
.then((post) => console.log(post.title))
.catch((error) => console.error(error.message));
response.ok는 HTTP 상태 코드가 200~299인지 알려 줍니다. 네트워크는 괜찮지만 서버가 404를 돌려줄 수 있으므로 꼭 확인해야 합니다.
const status = document.querySelector(".status");
const list = document.querySelector(".post-list");
async function loadPosts() {
try {
status?.textContent = "불러오는 중";
const response = await fetch("https://jsonplaceholder.typicode.com/posts?_limit=5");
if (!response.ok) throw new Error("네트워크 오류");
const posts = await response.json();
if (list) {
list.innerHTML = posts
.map((post) => `<li class="post-item">${post.title}</li>`)
.join("");
}
if (status) status.textContent = "완료";
} catch (error) {
if (status) status.textContent = error instanceof Error ? error.message : "알 수 없는 오류";
}
}
loadPosts();
상태 텍스트만으로도 사용자에게 요청 상황을 알려 줄 수 있습니다. 나중에는 로딩 스켈레톤, 에러 안내 모달을 붙이면 됩니다.
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 4000);
try {
const response = await fetch("/api/attendance", { signal: controller.signal });
clearTimeout(timeout);
const data = await response.json();
renderAttendance(data);
} catch (error) {
if (error.name === "AbortError") {
showToast("네트워크가 느립니다. 다시 시도하세요.");
}
}
모바일 데이터 환경처럼 네트워크가 느린 경우에는 일정 시간이 지나면 요청을 취소하고 사용자에게 재시도를 권하는 것이 좋습니다.
async function loadDashboard() {
const [profile, todos] = await Promise.all([
fetch("/api/profile").then((res) => res.json()),
fetch("/api/todos").then((res) => res.json()),
]);
return { profile, todos };
}
Promise.all은 독립적인 여러 요청을 동시에 보낼 때 사용합니다. 응답이 모두 도착하면 배열 순서대로 결과를 돌려줍니다.
왜 중요한가
- 학교 행사 신청 페이지처럼 외부 데이터를 불러야 하는 UI에서는 로딩·에러 상태가 곧 사용자 신뢰입니다.
async/await구조를 익히면 코드 리뷰나 발표에서 “이 함수는 무엇을 기다리고, 언제 화면을 업데이트하는가”를 짧게 설명할 수 있습니다.- 느린 와이파이나 기숙사 네트워크처럼 품질이 들쭉날쭉한 환경에서
AbortController를 적용하면 앱이 멈춘 듯 보이는 상황을 줄일 수 있습니다.
실습
- 따라 하기: JSONPlaceholder
posts엔드포인트를 호출해 로딩/완료/에러 텍스트를 순서대로 갱신합니다. - 확장하기:
Promise.all로/posts,/users를 동시에 불러와 카드 UI와 작성자 이름을 함께 렌더링합니다. - 디버깅: 잘못된 URL을 넣어 HTTP 404를 발생시키고,
response.ok검사를 뺐을 때 어떤 문제가 생기는지 비교합니다. - 완료 기준: 로딩 문구 → 데이터 렌더 → 에러 메시지 또는 재시도 버튼 흐름을 콘솔 경고 없이 재현하면 실습이 끝납니다.
마무리
fetch와 async/await만 확실히 잡아도 백엔드와 화면을 직접 연결할 수 있습니다. 다음 글에서는 받은 데이터를 구조화하고 모듈로 나누는 방법을 살펴보겠습니다.
💬 댓글
이 글에 대한 의견을 남겨주세요