1. fetch() 함수
1️⃣ fetch() 란?
URL에 요청을 보내고, Promise를 반환하여 서버에서 받은 응답을 비동기적으로 처리하게 해주는 함수
fetch(url, options?)- url: 요청 보낼 주소 (필수)
- options: HTTP 메서드, 헤더, 바디 등 추가 설정 (선택)
2️⃣ fetch() 인수
1) url (필수)
요청을 보낼 리소스 주소(문자열)
fetch("https://example.com/data");2) options (선택)
요청 방식이나 헤더, 바디 등을 설정하는 객체
대표 옵션:
fetch("https://example.com/data", {
method: "POST", // GET, POST, PUT, DELETE 등
headers: { // 요청 헤더
"Content-Type": "application/json"
},
body: JSON.stringify({ // 요청 바디 (GET에는 보통 사용 X)
name: "ChatGPT"
}),
mode: "cors", // cors, no-cors, same-origin
credentials: "include", // 쿠키 포함 방식
cache: "no-cache", // 캐시 전략
redirect: "follow", // redirect 정책
});옵션 | 역할 | 예시 값 |
method | 요청 방식 지정 | "GET", "POST", "PUT", "DELETE" |
headers | 요청 헤더 설정 | { "Content-Type": "application/json" } |
body | 서버에 보낼 데이터(POST 등에서 사용) | "text", JSON.stringify(obj) |
mode | CORS 정책 설정 | "cors", "no-cors", "same-origin" |
credentials | 쿠키/자격 증명 포함 여부 | "omit", "same-origin", "include" |
cache | 캐시 사용 방식 | "no-cache", "reload", "force-cache" |
redirect | 리다이렉트 처리 방식 | "follow", "error", "manual" |
referrer | 요청 referrer 설정 | "about:client", URL |
referrerPolicy | referrer를 어떻게 보낼지 정책 | "no-referrer", "strict-origin" 등 |
signal | 요청 취소용 AbortSignal | AbortController().signal |
keepalive | 페이지 종료 후에도 요청 유지 | true/false |
integrity | Subresource Integrity 체크 | "sha256-..." |
3️⃣ fetch() 특징
1) Promise 기반
fetch()는 Promise를 반환fetch("/data.json")
.then(res => res.json())
.then(data => console.log(data));2) async/await 사용 가능
async function loadData() {
const res = await fetch("/data.json");
const data = await res.json();
console.log(data);
}4️⃣ 응답(Response) 처리
fetch()의 반환값은 Response 객체fetch(url).then(response => {
console.log(response.status); // 상태 코드
});응답 데이터를 읽을 때는 다음 메서드를 사용
.json()— JSON 데이터
.text()— 문자열
.blob()— 파일/이미지
.arrayBuffer()— 바이너리
.formData()— 폼 데이터
5️⃣ 주의할 점
1) ❌ HTTP 오류(400, 500)를 자동으로 throw 하지 않음
const res = await fetch(url);
if (!res.ok) {
throw new Error("Request failed: " + res.status);
}2) ⭕ 네트워크 오류만 fetch가 reject 처리함
6️⃣ 요약
특징 | 설명 |
역할 | HTTP 요청을 보내고 응답을 받는 표준 API |
반환 | Promise |
장점 | 비동기 처리 쉬움, Response 데이터 다양한 형태로 변환 가능 |
단점 | HTTP 에러 자동 throw 안 함 |
2. 수업 내용 정리 및 실습
1️⃣ fetch & 렌더링 동작 요약
1) HTML & JS 렌더링
- HTML은 브라우저가 위에서 아래로 해석하며 화면을 그림
- 동기 자바스크립트 코드가 실행 중이면 렌더링이 중단됨 (JS는 단일 스레드로 실행되기 때문)
2) fetch() 의 비동기 처리
fetch()는 즉시 Promise를 반환
- 네트워크 작업은 Web API에서 백그라운드 수행
- 자바스크립트 실행과 화면 렌더링은 멈추지 않고 계속 진행
3) 동기 네트워크라면?
만약 fetch가 동기 방식이었다면
- 다운로드가 끝날 때까지 JS 실행 멈춤
- 화면 그리기 중단
- 사용자는 “멈춘 화면”을 보게 됨
➡️ 그래서 fetch는 비동기
4) 함수 종료 후에도 데이터를 쓸 수 있는 이유
- fetch 호출 함수가 끝나도
- Promise에 연결된
.then()/await가 나중에 실행
- 그 시점에
Response객체와 데이터에 접근 가능
5) Promise & Response의 관계
Promise는 미래의 값을 약속하는 껍데기(pending상태)
- 응답이 오면 Promise는
fulfilled상태가 되고 실제 데이터는 Response 객체에 들어 있음 response.headersresponse.body(스트림)response.json()호출로 본문 파싱
2️⃣ 실습
1) fetch() → promise(pending 상태 → fulfilled 상태)
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
<style>
div {
border: 1px solid black;
padding: 10px;
}
</style>
</head>
<body>
<div class="container">
<div id="user-1"></div>
</div>
<script>
function download() {
let res = fetch("https://jsonplaceholder.typicode.com/posts/1");
console.log(res);
}
download();
</script>
</body>
</html>
2) pending 상태에서 json 파싱하면?
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
<style>
div {
border: 1px solid black;
padding: 10px;
}
</style>
</head>
<body>
<div class="container">
<div id="user-1"></div>
</div>
<script>
function download() {
let res = fetch("https://jsonplaceholder.typicode.com/posts/1");
let resBody = res.json(); // JavaScript Object로 변환
console.log(resBody);
}
download();
// while (true) {}
</script>
</body>
</html>
➡️ resBody는 아직 pending 상태이기 때문에 json으로 파싱할 데이터가 없다고 뜸!
이걸 해결해야 함 → 그게 async / await
⚠️
while (true) {} 를 넣으면 그림 그리는 게 끝나지 않기 때문에 화면에 아무것도 뜨지 않고 fetch로 돌아갈 수 없음!3) async / await
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
<style>
div {
border: 1px solid black;
padding: 10px;
}
</style>
</head>
<body>
<div class="container">
<div id="user-1"></div>
</div>
<script>
async function download() {
let res = await fetch("https://jsonplaceholder.typicode.com/posts/1");
let resBody = await res.json(); // JavaScript Object로 변환
console.log(resBody);
}
download();
</script>
</body>
</html>

※ async / await 동작 이해하기
① async 함수란?
async가 붙은 함수는 항상 Promise를 반환
- 내부 코드가 비동기 흐름을 제어할 수 있음
② await의 역할
await은 해당 Promise가 완료될 때까지 그 줄에서 함수 실행을 잠시 멈춤
- 하지만 자바스크립트 전체가 멈추는 게 아니라 그 async 함수만 잠시 멈춤!
③ 실행 흐름
download()호출
- 함수 안에서
fetch()실행 → 바로 Promise 반환
- 네트워크 요청은 **브라우저(Web API)**가 처리
- JS는 함수 실행을 중단하고 다음 코드 실행/렌더링(함수의 큐와 스택 모두 남겨놓은 상태)
- 네트워크 끝나면 Promise 상태 변경 (
pending → fulfilled)
- 이벤트 루프가 async 함수의 대기 지점 이후 코드를 다시 실행
4) json 파싱
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
<style>
div {
border: 1px solid black;
padding: 10px;
}
</style>
</head>
<body>
<div class="container">
<div id="post-1"></div>
</div>
<script>
async function download() {
let res = await fetch("https://jsonplaceholder.typicode.com/posts/1");
console.log(res);
// 통신으로 받는 데이터의 타입은 JSON!(타입 확인용 코드)
const contentType = res.headers.get("content-type");
console.log(contentType);
// json 파싱
let post = await res.json(); // json문자열을 javascript object로 변환
console.log(post);
let dom = document.querySelector("#post-1");
dom.innerHTML = `
userId : ${post.userId} <br>
id : ${post.id} <br>
title : ${post.title} <br>
body : ${post.body} <br>
`;
}
download();
</script>
</body>
</html>
⚠️ 파싱하려면 먼저 브라우저 콘솔에서 json 내부를 확인 → 해당 json에 맞게
document.querySelector, dom.innerHTML 해야 함5) 통신으로 받은 데이터가 여러 건일 때
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
<style>
div {
border: 1px solid black;
padding: 10px;
}
.container {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 10px;
}
.post {
border: 2px solid #4a90e2;
border-radius: 8px;
padding: 20px;
background: transparent;
transition: border-color 0.2s, box-shadow 0.2s;
}
.post:hover {
border-color: #357abd;
box-shadow: 0 0 0 3px rgba(74, 144, 226, 0.1);
}
.meta-info {
display: flex;
gap: 15px;
margin-bottom: 12px;
font-size: 12px;
color: #666;
}
.meta-item {
background: transparent;
padding: 4px 10px;
border-radius: 12px;
font-weight: 500;
border: 1px solid #d0d0d0;
}
.title {
font-size: 18px;
font-weight: bold;
color: #333;
margin-bottom: 12px;
line-height: 1.4;
}
.body {
font-size: 14px;
color: #555;
line-height: 1.6;
text-align: justify;
}
</style>
</head>
<body>
<div id="post-box" class="container"></div>
<script>
function renderUserItem(post) {
let item = document.createElement("div");
item.setAttribute("id", `post-${post.id}`);
item.setAttribute("class", `post`);
item.innerHTML = `<div class="meta-info">
<span class="meta-item">userId: ${post.userId}</span>
<span class="meta-item">id: ${post.id}</span>
</div>
<div class="title">
${post.title}
</div>
<div class="body">
${post.body}
</div>`;
return item;
}
async function download() {
// 1. fetch
let res = await fetch("https://jsonplaceholder.typicode.com/posts");
// 2. 파싱 (json Array -> list)
let posts = await res.json();
// 3. 랜더링
let dom = document.querySelector("#post-box");
posts.forEach((post) => {
dom.append(renderUserItem(post));
});
}
download();
</script>
</body>
</html>
6) 브라우저 콘솔에서 특정 ID로 삭제

7) ID로 delete (상태 관리 X)
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
<style>
div {
border: 1px solid black;
padding: 10px;
}
.container {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 10px;
}
#user-1 {
border: 2px solid #4a90e2;
border-radius: 8px;
padding: 20px;
background: transparent;
transition: border-color 0.2s, box-shadow 0.2s;
}
#user-1:hover {
border-color: #357abd;
box-shadow: 0 0 0 3px rgba(74, 144, 226, 0.1);
}
.meta-info {
display: flex;
gap: 15px;
margin-bottom: 12px;
font-size: 12px;
color: #666;
}
.meta-item {
background: transparent;
padding: 4px 10px;
border-radius: 12px;
font-weight: 500;
border: 1px solid #d0d0d0;
}
.title {
font-size: 18px;
font-weight: bold;
color: #333;
margin-bottom: 12px;
line-height: 1.4;
}
.body {
font-size: 14px;
color: #555;
line-height: 1.6;
text-align: justify;
}
</style>
</head>
<body>
<div>
<input type="text" id="my-input" />
<button type="button" onclick="del()">id로삭제</button>
</div>
<div id="post-box" class="container"></div>
<script>
function del() {
let id = document.querySelector("#my-input").value;
let delDom = document.querySelector(`#post-${id}`);
delDom.remove();
}
function renderUserItem(post) {
let item = document.createElement("div");
item.setAttribute("id", `post-${post.id}`);
item.innerHTML = `<div class="meta-info">
<span class="meta-item">userId: ${post.userId}</span>
<span class="meta-item">id: ${post.id}</span>
</div>
<div class="title">
${post.title}
</div>
<div class="body">
${post.body}
</div>`;
return item;
}
async function download() {
// 1. fetch
let res = await fetch("https://jsonplaceholder.typicode.com/posts");
// 2. 파싱 (json Array -> list)
let posts = await res.json();
// 3. 랜더링
let dom = document.querySelector("#post-box");
posts.forEach((post) => {
dom.append(renderUserItem(post));
});
}
download();
</script>
</body>
</html>
⚠️ 이렇게 한 번 삭제하면 새로 고침을 하지 않으면(즉, 통신을 다시 하지 않으면) 원래 상태로 돌릴 수 없음
➡️ 기존 상태를 별도의 변수로 관리하면 통신 다시 하지 않아도 원래 상태로 돌릴 수 있음
⬇️
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
<style>
div {
border: 1px solid black;
padding: 10px;
}
.container {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 10px;
}
#user-1 {
border: 2px solid #4a90e2;
border-radius: 8px;
padding: 20px;
background: transparent;
transition: border-color 0.2s, box-shadow 0.2s;
}
#user-1:hover {
border-color: #357abd;
box-shadow: 0 0 0 3px rgba(74, 144, 226, 0.1);
}
.meta-info {
display: flex;
gap: 15px;
margin-bottom: 12px;
font-size: 12px;
color: #666;
}
.meta-item {
background: transparent;
padding: 4px 10px;
border-radius: 12px;
font-weight: 500;
border: 1px solid #d0d0d0;
}
.title {
font-size: 18px;
font-weight: bold;
color: #333;
margin-bottom: 12px;
line-height: 1.4;
}
.body {
font-size: 14px;
color: #555;
line-height: 1.6;
text-align: justify;
}
</style>
</head>
<body>
<div>
<input type="text" id="my-input" />
<button type="button" onclick="del()">id로삭제</button>
</div>
<div id="post-box" class="container"></div>
<script>
function del() {
let id = document.querySelector("#my-input").value;
let delDom = document.querySelector(`#post-${id}`);
delDom.remove();
}
function renderUserItem(post) {
let item = document.createElement("div");
item.setAttribute("id", `post-${post.id}`);
item.innerHTML = `<div class="meta-info">
<span class="meta-item">userId: ${post.userId}</span>
<span class="meta-item">id: ${post.id}</span>
</div>
<div class="title">
${post.title}
</div>
<div class="body">
${post.body}
</div>`;
return item;
}
let state;
async function download() {
// 1. fetch
let res = await fetch("https://jsonplaceholder.typicode.com/posts");
// 2. 파싱 (json Array -> list)
let posts = await res.json();
state = posts;
// 3. 랜더링
let dom = document.querySelector("#post-box");
posts.forEach((post) => {
dom.append(renderUserItem(post));
});
}
download();
</script>
</body>
</html>
➡️ 장점 : 지우고자 하는 부분만 삭제 가능
➡️ 단점 : id로 삭제하면 삭제하기는 편하지만 원래 데이터로 복구가 안됨(데이터 관리가 안되기 때문)
원래 데이터 보려면 통신을 다시 해야 함
8) 상태로 관리하는 방법(검색)
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
<style>
div {
border: 1px solid black;
padding: 10px;
}
.container {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 10px;
}
#user-1 {
border: 2px solid #4a90e2;
border-radius: 8px;
padding: 20px;
background: transparent;
transition: border-color 0.2s, box-shadow 0.2s;
}
#user-1:hover {
border-color: #357abd;
box-shadow: 0 0 0 3px rgba(74, 144, 226, 0.1);
}
.meta-info {
display: flex;
gap: 15px;
margin-bottom: 12px;
font-size: 12px;
color: #666;
}
.meta-item {
background: transparent;
padding: 4px 10px;
border-radius: 12px;
font-weight: 500;
border: 1px solid #d0d0d0;
}
.title {
font-size: 18px;
font-weight: bold;
color: #333;
margin-bottom: 12px;
line-height: 1.4;
}
.body {
font-size: 14px;
color: #555;
line-height: 1.6;
text-align: justify;
}
</style>
</head>
<body>
<div>
<input type="text" id="my-input" />
<button type="button" onclick="search()">id로 검색</button>
<button type="button" onclick="list()">전체목록</button>
</div>
<div id="post-box" class="container"></div>
<script>
let state;
function list() {
let dom = document.querySelector("#post-box");
dom.innerHTML = "";
state.forEach((post) => {
dom.append(renderUserItem(post));
});
}
function search() {
let id = document.querySelector("#my-input").value;
let newState = state.filter((post) => post.id == id);
let dom = document.querySelector("#post-box");
dom.innerHTML = "";
newState.forEach((post) => {
dom.append(renderUserItem(post));
});
}
function renderUserItem(post) {
let item = document.createElement("div");
item.innerHTML = `<div class="meta-info">
<span class="meta-item">userId: ${post.userId}</span>
<span class="meta-item">id: ${post.id}</span>
</div>
<div class="title">
${post.title}
</div>
<div class="body">
${post.body}
</div>`;
return item;
}
async function download() {
// 1. fetch
let res = await fetch("https://jsonplaceholder.typicode.com/posts");
// 2. 파싱 (json Array -> list)
let posts = await res.json();
state = posts;
// 3. 랜더링
let dom = document.querySelector("#post-box");
state.forEach((post) => {
dom.append(renderUserItem(post));
});
}
download();
</script>
</body>
</html>



➡️ 단점 : 상태로 관리하면 원래 있던 상태 다 날리고 다시 그려야 한다!
나중에 리액트 쓰면 보완가능
9) 상태로 관리하는 방법(삭제)
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
<style>
div {
border: 1px solid black;
padding: 10px;
}
.container {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 10px;
}
#user-1 {
border: 2px solid #4a90e2;
border-radius: 8px;
padding: 20px;
background: transparent;
transition: border-color 0.2s, box-shadow 0.2s;
}
#user-1:hover {
border-color: #357abd;
box-shadow: 0 0 0 3px rgba(74, 144, 226, 0.1);
}
.meta-info {
display: flex;
gap: 15px;
margin-bottom: 12px;
font-size: 12px;
color: #666;
}
.meta-item {
background: transparent;
padding: 4px 10px;
border-radius: 12px;
font-weight: 500;
border: 1px solid #d0d0d0;
}
.title {
font-size: 18px;
font-weight: bold;
color: #333;
margin-bottom: 12px;
line-height: 1.4;
}
.body {
font-size: 14px;
color: #555;
line-height: 1.6;
text-align: justify;
}
</style>
</head>
<body>
<div>
<input type="text" id="my-input" />
<button type="button" onclick="del()">id로 삭제</button><br />
<button type="button" onclick="list()">전체목록</button><br />
</div>
<div id="post-box" class="container"></div>
<script>
// 상태관리
let state; // 현재상태
let prev; // 최초상태
let next; // 변경된 미래 상태
// 전체목록보기
function list() {
let dom = document.querySelector("#post-box");
dom.innerHTML = "";
// 현재상태 갱신
state = prev;
// 최초상태
prev.forEach((post) => {
dom.append(renderUserItem(post));
});
}
// 삭제하기
function del() {
// 값을 안받으면 공백임
let id = document.querySelector("#my-input").value;
// 유효성 검사
// if (id == "") {
// alert("삭제할 id를 입력하세요");
// return;
// }
next = state.filter((post) => post.id != id);
let dom = document.querySelector("#post-box");
// if 로직으로 next 상태와 state 현재 상태를 비교해 볼 수 있음.
if (next.length != state.length) {
state = next; // 현재 상태 갱신
dom.innerHTML = "";
next.forEach((post) => {
dom.append(renderUserItem(post));
});
} else {
alert("변경된 데이터가 없어요");
}
document.querySelector("#my-input").value = "";
document.querySelector("#my-input").focus();
}
// Item 디자인 리턴해주는 함수
function renderUserItem(post) {
let item = document.createElement("div");
item.innerHTML = `<div class="meta-info">
<span class="meta-item">userId: ${post.userId}</span>
<span class="meta-item">id: ${post.id}</span>
</div>
<div class="title">
${post.title}
</div>
<div class="body">
${post.body}
</div>`;
return item;
}
// 데이터 다운로드 함수 (최초에 단 한번만 실행됨)
async function download() {
// 1. fetch
let res = await fetch("https://jsonplaceholder.typicode.com/posts");
// 2. 파싱 (json Array -> list)
let posts = await res.json();
prev = posts; // 최초 원본 데이터 보관
state = prev; // 현재 상태 보관
// 3. 랜더링
let dom = document.querySelector("#post-box");
state.forEach((post) => {
dom.append(renderUserItem(post));
});
}
// 다운로드 실행
download();
</script>
</body>
</html>



Share article