Ver3.5 ๐ AJAX์ Fetch API๋ฅผ ํ์ฉํ๋น๋๊ธฐ ๋ฐ์ดํฐ ํต์

๐ AJAX์ Fetch API๋ฅผ ํ์ฉํ
๋น๋๊ธฐ ๋ฐ์ดํฐ ํต์
์๋ฒ๋ ๋ชฐ๋ ๋ํํ๋ ๋ฒ โ ํ์ด์ง ์๋ก๊ณ ์นจ ์์ด ๋ฐ์ดํฐ ์ฃผ๊ณ ๋ฐ๊ธฐ ์์ ์ ๋ณต ๐
์ผ, ์์งํ ๋งํด๋ด. ์น์ฌ์ดํธ ์ฐ๋ค๊ฐ ๋ฒํผ ํ๋ ๋๋ ๋๋ฐ ํ์ด์ง ์ ์ฒด๊ฐ ํ์๊ฒ ๊น๋นก์ด๋ฉด์ ์๋ก๊ณ ์นจ๋๋ ๊ฑฐ ์ง์ง ๋ต๋ตํ์ง ์์? ๐ค
๊ทธ๋ฐ๋ฐ ์์ฆ ์ ํ๋ธ, ์ธ์คํ, ์นด์นด์คํก ์น๋ฒ์ ๊ฐ์ ์๋น์ค๋ค์ ๋ฒํผ ๋๋ฌ๋ ํ์ด์ง๊ฐ ์ ํ ์ ๊น๋นก์ด์์. ๋๊ธ ๋ฌ์๋, ์ข์์ ๋๋ฌ๋, ๊ฒ์ํด๋ โ ๊ทธ๋ฅ ์ค๋ฅด๋ฅต ๋ฐ์ดํฐ๊ฐ ๋ฐ๋์ง.
์ด๊ฒ ๋ฐ๋ก ๋น๋๊ธฐ ๋ฐ์ดํฐ ํต์ ์ ๋ง๋ฒ์ด์ผ. ๊ทธ๋ฆฌ๊ณ ๊ทธ ๋ง๋ฒ์ ํต์ฌ ์ฃผ๋ฌธ์ด ๋ฐ๋ก AJAX์ Fetch API์ผ. ์ค๋์ ์ด ๋ ๋
์์ ์์ ํ ํํค์ณ๋ณผ ๊ฑฐ์ผ. ์ด๋ ต๊ฒ ์๊ฐํ์ง ๋ง โ ์น๊ตฌํํ
์ค๋ช
ํ๋ฏ์ด ์ฝ๊ณ ์ฌ๋ฏธ์๊ฒ ํ์ด์ค๊ฒ! ๐
๐ฐ๏ธ AJAX๊ฐ ๋ญ๋ฐ? ์ญ์ฌ๋ถํฐ ์ง๊ณ ๊ฐ์
AJAX๋ Asynchronous JavaScript And XML์ ์ฝ์์ผ. ์ด๋ฆ๋ง ๋ณด๋ฉด XML์ด ํต์ฌ์ธ ๊ฒ ๊ฐ์ง๋ง, ์์ฆ์ XML๋ณด๋ค JSON์ ํจ์ฌ ๋ง์ด ์จ. ์ด๋ฆ์ด ์ข ๊ตฌ์์ด๊ธด ํ๋ฐ, ๊ฐ๋ ์์ฒด๋ ์ฌ์ ํ ํ์ญ์ด์ผ! ๐
AJAX์ ์ญ์ฌ๋ฅผ ์ ๊น ์ดํด๋ณด๋ฉด โ 1999๋
์ ๋ง์ดํฌ๋ก์ํํธ๊ฐ XMLHttpRequest(XHR) ๊ฐ์ฒด๋ฅผ ์ฒ์ ๋ง๋ค์์ด. ๊ทธ๋ฆฌ๊ณ 2005๋
์ ๊ตฌ๊ธ์ด Gmail๊ณผ Google Maps์ ์ด ๊ธฐ์ ์ ์ ๊ทน ํ์ฉํ๋ฉด์ ์ธ์์ด "์ด? ์ด๊ฑฐ ๋ญ์ผ ์ ๊ธฐํ๋ฐ?" ํ๊ณ ์ฃผ๋ชฉํ๊ธฐ ์์ํ์ง.
๊ทธ ์ดํ๋ก ์น ๊ฐ๋ฐ์ ํจ๋ฌ๋ค์์ด ์์ ํ ๋ฐ๋์์ด. ์ด์ ๋ AJAX ์๋ ์น ๊ฐ๋ฐ์ ์์๋ ๋ชป ํ ์ ๋์ผ.
๐ก XMLHttpRequest โ AJAX์ ์์กฐ
AJAX๋ฅผ ๊ตฌํํ๋ ์ ํต์ ์ธ ๋ฐฉ๋ฒ์ XMLHttpRequest(XHR) ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ๋ ๊ฑฐ์ผ. ์ฝ๋๋ฅผ ํ๋ฒ ๋ด๋ณผ๊น?
// XMLHttpRequest ๊ธฐ๋ณธ ์ฌ์ฉ๋ฒ
const xhr = new XMLHttpRequest();
// ์์ฒญ ์ด๊ธฐํ: ๋ฉ์๋, URL, ๋น๋๊ธฐ ์ฌ๋ถ
xhr.open('GET', 'https://api.example.com/data', true);
// ์์ฒญ ์๋ฃ ์ ์คํ๋ ์ฝ๋ฐฑ
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
const data = JSON.parse(xhr.responseText);
console.log('๋ฐ์ ๋ฐ์ดํฐ:', data);
}
};
// ์๋ฌ ์ฒ๋ฆฌ
xhr.onerror = function() {
console.error('์์ฒญ ์คํจ!');
};
// ์์ฒญ ์ ์ก
xhr.send();
๋ณด์ด์ง? ์์งํ ์ข ๋ณต์กํ์ง ์์? ๐
readyState๊ฐ 4์ธ์ง ํ์ธํ๊ณ , status๊ฐ 200์ธ์ง ํ์ธํ๊ณ ... ์ด๊ฒ ๋ค ๋ญ ์๋ฆฌ์ผ ์ถ์ ์ ์์ด. ๊ทธ๋์ readyState ๊ฐ์ ํ๋ก ์ ๋ฆฌํด์ค๊ฒ.
| readyState ๊ฐ | ์ํ | ์ค๋ช |
|---|---|---|
| 0 | UNSENT | XHR ๊ฐ์ฒด ์์ฑ๋จ, open() ๋ฏธํธ์ถ |
| 1 | OPENED | open() ํธ์ถ๋จ |
| 2 | HEADERS_RECEIVED | ์๋ฒ ์๋ต ํค๋ ์์ ๋จ |
| 3 | LOADING | ์๋ต ๋ณธ๋ฌธ ์์ ์ค |
| 4 | DONE | โ ์๋ฃ! ์ด๋ ๋ฐ์ดํฐ ์ฒ๋ฆฌํ๋ฉด ๋ผ |
โจ Fetch API โ ํ๋์ ์ด๊ณ ์ธ๋ จ๋ ๋ฐฉ์
Fetch API๋ 2015๋ ์ ๋ฑ์ฅํ ํ๋์ ์ธ ๋น๋๊ธฐ ํต์ ๋ฐฉ๋ฒ์ด์ผ. XHR์ ๋ณต์กํจ์ ์น ๊ฑท์ด๋ด๊ณ , Promise ๊ธฐ๋ฐ์ผ๋ก ๋ง๋ค์ด์ก์ด. ์ฝ๋๊ฐ ํจ์ฌ ๊น๋ํ๊ณ ์ฝ๊ธฐ ์ฌ์์ก์ง.
โข ๊ธฐ๋ณธ ๋ด์ฅ โ ๋ณ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค์น ๋ถํ์
โข ์คํธ๋ฆฌ๋ฐ ์ง์, ์์ฒญ/์๋ต ๊ฐ์ฒด ๋ถ๋ฆฌ
โข ์ฝ๋๊ฐ ์ง๊ด์ ์ด๊ณ ์ฝ๊ธฐ ์ฌ์
๐ฐ Fetch ๊ธฐ๋ณธ ์ฌ์ฉ๋ฒ
// ๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ Fetch ์ฌ์ฉ๋ฒ
fetch('https://api.example.com/data')
.then(response => {
// response๋ HTTP ์๋ต ๊ฐ์ฒด
// ์ค์ ๋ฐ์ดํฐ๋ฅผ ๊บผ๋ด๋ ค๋ฉด .json() ํธ์ถ ํ์
return response.json();
})
.then(data => {
console.log('๋ฐ์ ๋ฐ์ดํฐ:', data);
})
.catch(error => {
console.error('์๋ฌ ๋ฐ์:', error);
});
XHR์ด๋ ๋น๊ตํ๋ฉด ํจ์ฌ ๊น๋ํ์ง? ๐ ๊ทธ๋ฐ๋ฐ ์ฌ๊ธฐ์ ์ค์ํ ํฌ์ธํธ๊ฐ ์์ด. fetch()๋ HTTP ์๋ฌ(404, 500 ๋ฑ)๋ฅผ ์๋์ผ๋ก rejectํ์ง ์์. ์ด๊ฒ ์ด๋ณด์๋ค์ด ์์ฃผ ์ค์ํ๋ ๋ถ๋ถ์ด์ผ!
.catch()๊ฐ ์คํ๋์ง ์์. response.ok๋ฅผ ์ง์ ํ์ธํด์ผ ํด!
// ์ฌ๋ฐ๋ฅธ ์๋ฌ ์ฒ๋ฆฌ ๋ฐฉ๋ฒ
fetch('https://api.example.com/data')
.then(response => {
// response.ok๋ status๊ฐ 200-299์ผ ๋ true
if (!response.ok) {
throw new Error(`HTTP ์๋ฌ! ์ํ์ฝ๋: ${response.status}`);
}
return response.json();
})
.then(data => {
console.log('์ฑ๊ณต:', data);
})
.catch(error => {
// ๋คํธ์ํฌ ์๋ฌ OR ์์์ throwํ ์๋ฌ ๋ชจ๋ ์ฒ๋ฆฌ
console.error('์๋ฌ:', error.message);
});
๐ async/await์ ํจ๊ป ์ฐ๋ฉด ๋ ์๋ป!
Promise ์ฒด์ธ๋ ์ข์ง๋ง, async/await๋ฅผ ์ฐ๋ฉด ๋ง์น ๋๊ธฐ ์ฝ๋์ฒ๋ผ ์ฝํ๋ฉด์๋ ๋น๋๊ธฐ๋ก ๋์ํด. ์ฝ๋๊ฐ ํจ์ฌ ์ง๊ด์ ์ด์ผ!
// async/await ๋ฒ์ โ ํจ์ฌ ์ฝ๊ธฐ ์ฝ์ง?
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
if (!response.ok) {
throw new Error(`HTTP ์๋ฌ! ์ํ์ฝ๋: ${response.status}`);
}
const data = await response.json();
console.log('๋ฐ์ ๋ฐ์ดํฐ:', data);
return data;
} catch (error) {
console.error('์๋ฌ ๋ฐ์:', error.message);
}
}
// ํธ์ถ
fetchData();
๐ฌ HTTP ๋ฉ์๋๋ณ Fetch ํ์ฉ๋ฒ
์น์์ ์๋ฒ์ ํต์ ํ ๋๋ ๋ค์ํ HTTP ๋ฉ์๋๋ฅผ ์ฌ์ฉํด. GET, POST, PUT, DELETE... ๊ฐ๊ฐ ์ธ์ ์ฐ๋์ง, Fetch๋ก ์ด๋ป๊ฒ ๊ตฌํํ๋์ง ์์๋ณด์!
๐ฅ GET โ ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ
๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ ๋ฉ์๋์ผ. ์๋ฒ์์ ๋ฐ์ดํฐ๋ฅผ ์ฝ์ด์ฌ ๋ ์ฌ์ฉํด. Fetch์ ๊ธฐ๋ณธ๊ฐ์ด GET์ด๋ผ์ ๋ฐ๋ก ๋ช ์ ์ ํด๋ ๋ผ.
// GET ์์ฒญ โ ์ฌ์ฉ์ ๋ชฉ๋ก ๊ฐ์ ธ์ค๊ธฐ
async function getUsers() {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/users');
if (!response.ok) throw new Error('์๋ฒ ์๋ฌ');
const users = await response.json();
users.forEach(user => {
console.log(`์ด๋ฆ: ${user.name}, ์ด๋ฉ์ผ: ${user.email}`);
});
} catch (error) {
console.error('์ ์ ๋ชฉ๋ก ๋ถ๋ฌ์ค๊ธฐ ์คํจ:', error);
}
}
getUsers();
๐ค POST โ ๋ฐ์ดํฐ ๋ณด๋ด๊ธฐ
์๋ฒ์ ์ ๋ฐ์ดํฐ๋ฅผ ์์ฑํ ๋ ์ฌ์ฉํด. ํ์๊ฐ์ , ๊ฒ์๊ธ ์์ฑ, ๋๊ธ ๋ฌ๊ธฐ ๋ฑ์ด ์ฌ๊ธฐ์ ํด๋นํด.
// POST ์์ฒญ โ ์ ๊ฒ์๊ธ ์์ฑ
async function createPost(title, body, userId) {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST', // ๋ฉ์๋ ์ง์
headers: {
'Content-Type': 'application/json', // JSON ๋ฐ์ดํฐ ์ ์ก ๋ช
์
},
body: JSON.stringify({ // ๊ฐ์ฒด๋ฅผ JSON ๋ฌธ์์ด๋ก ๋ณํ
title: title,
body: body,
userId: userId
})
});
if (!response.ok) throw new Error('๊ฒ์๊ธ ์์ฑ ์คํจ');
const newPost = await response.json();
console.log('์ ๊ฒ์๊ธ ์์ฑ๋จ:', newPost);
console.log('์์ฑ๋ ID:', newPost.id);
} catch (error) {
console.error('์๋ฌ:', error);
}
}
createPost('์๋
ํ์ธ์!', '์ฒซ ๋ฒ์งธ ๊ฒ์๊ธ์
๋๋ค.', 1);
โ๏ธ PUT / PATCH โ ๋ฐ์ดํฐ ์์ ํ๊ธฐ
PUT์ ๋ฐ์ดํฐ ์ ์ฒด๋ฅผ ๊ต์ฒด, PATCH๋ ์ผ๋ถ๋ง ์์ ํ ๋ ์จ. ๊ฒ์๊ธ ์์ , ํ๋กํ ์ ๋ฐ์ดํธ ๋ฑ์ ์ฌ์ฉํด.
// PATCH ์์ฒญ โ ๊ฒ์๊ธ ์ ๋ชฉ๋ง ์์
async function updatePostTitle(postId, newTitle) {
try {
const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${postId}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
title: newTitle // ์ ๋ชฉ๋ง ๋ณ๊ฒฝ
})
});
if (!response.ok) throw new Error('์์ ์คํจ');
const updatedPost = await response.json();
console.log('์์ ๋ ๊ฒ์๊ธ:', updatedPost);
} catch (error) {
console.error('์์ ์๋ฌ:', error);
}
}
updatePostTitle(1, '์์ ๋ ์ ๋ชฉ์
๋๋ค!');
๐๏ธ DELETE โ ๋ฐ์ดํฐ ์ญ์ ํ๊ธฐ
// DELETE ์์ฒญ โ ๊ฒ์๊ธ ์ญ์
async function deletePost(postId) {
try {
const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${postId}`, {
method: 'DELETE'
});
if (!response.ok) throw new Error('์ญ์ ์คํจ');
// DELETE๋ ๋ณดํต ๋น ์๋ต์ด๋ 204 No Content ๋ฐํ
console.log(`๊ฒ์๊ธ ${postId} ์ญ์ ์๋ฃ! โ
`);
} catch (error) {
console.error('์ญ์ ์๋ฌ:', error);
}
}
deletePost(1);
| ๋ฉ์๋ | ์ฉ๋ | body ํ์? | ๋ฉฑ๋ฑ์ฑ |
|---|---|---|---|
| GET | ๋ฐ์ดํฐ ์กฐํ | โ | โ ์์ |
| POST | ๋ฐ์ดํฐ ์์ฑ | โ | โ ์์ |
| PUT | ์ ์ฒด ์์ | โ | โ ์์ |
| PATCH | ๋ถ๋ถ ์์ | โ | ์กฐ๊ฑด๋ถ |
| DELETE | ๋ฐ์ดํฐ ์ญ์ | โ | โ ์์ |
๐ ํค๋(Headers)์ ์ธ์ฆ ์ฒ๋ฆฌ
์ค์ ํ๋ก์ ํธ์์๋ ๋จ์ํ ๋ฐ์ดํฐ๋ง ์ฃผ๊ณ ๋ฐ๋ ๊ฒ ์๋์ผ. ์ธ์ฆ ํ ํฐ์ ํค๋์ ๋ด์์ ๋ณด๋ด๊ฑฐ๋, ํน์ ํ์์ ๋ฐ์ดํฐ๋ฅผ ์์ฒญํ๋ ๋ฑ ๋ค์ํ ํค๋ ์ค์ ์ด ํ์ํด.
// Headers ๊ฐ์ฒด๋ฅผ ํ์ฉํ ์์ฒญ
async function fetchWithAuth(url) {
const token = localStorage.getItem('authToken'); // ์ ์ฅ๋ ํ ํฐ ๊ฐ์ ธ์ค๊ธฐ
const headers = new Headers({
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`, // JWT ํ ํฐ ์ธ์ฆ
'Accept': 'application/json',
'X-Custom-Header': '์ฌ๋ฅ๋ท-์ฑ' // ์ปค์คํ
ํค๋
});
try {
const response = await fetch(url, { headers });
if (response.status === 401) {
// ์ธ์ฆ ๋ง๋ฃ ์ฒ๋ฆฌ
console.log('๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค!');
redirectToLogin();
return;
}
if (!response.ok) throw new Error(`์๋ฌ: ${response.status}`);
return await response.json();
} catch (error) {
console.error('์ธ์ฆ ์์ฒญ ์คํจ:', error);
}
}
// ์ฌ์ฉ
const userData = await fetchWithAuth('https://api.example.com/profile');
Content-Type: application/json โ JSON ๋ฐ์ดํฐ ์ ์กโข
Authorization: Bearer {token} โ JWT ์ธ์ฆโข
Accept: application/json โ JSON ์๋ต ์์ฒญโข
Content-Type: multipart/form-data โ ํ์ผ ์
๋ก๋
๐ ๏ธ ์ค์ ์์ ์์ฃผ ์ฐ๋ ํจํด๋ค
๐ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ API ํด๋ผ์ด์ธํธ ๋ง๋ค๊ธฐ
๋งค๋ฒ fetch๋ฅผ ์ง์ ์ฐ๋ฉด ์ฝ๋๊ฐ ์ค๋ณต๋ผ. ๊ณตํต ๋ก์ง์ ๋ฌถ์ด์ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ํจ์๋ก ๋ง๋ค์ด๋๋ฉด ํจ์ฌ ํธํด!
// ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ API ํด๋ผ์ด์ธํธ
const apiClient = {
baseURL: 'https://api.example.com',
// ๊ณตํต ํค๋ ์์ฑ
getHeaders() {
return {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('token') || ''}`
};
},
// ๊ณตํต ์์ฒญ ์ฒ๋ฆฌ
async request(endpoint, options = {}) {
const url = `${this.baseURL}${endpoint}`;
const config = {
headers: this.getHeaders(),
...options
};
const response = await fetch(url, config);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.message || `HTTP ${response.status}`);
}
// 204 No Content๋ json() ํธ์ถ ๋ถํ์
if (response.status === 204) return null;
return response.json();
},
// ํธ์ ๋ฉ์๋๋ค
get(endpoint) {
return this.request(endpoint);
},
post(endpoint, data) {
return this.request(endpoint, {
method: 'POST',
body: JSON.stringify(data)
});
},
put(endpoint, data) {
return this.request(endpoint, {
method: 'PUT',
body: JSON.stringify(data)
});
},
patch(endpoint, data) {
return this.request(endpoint, {
method: 'PATCH',
body: JSON.stringify(data)
});
},
delete(endpoint) {
return this.request(endpoint, { method: 'DELETE' });
}
};
// ์ฌ์ฉ ์์
const users = await apiClient.get('/users');
const newUser = await apiClient.post('/users', { name: 'ํ๊ธธ๋', age: 25 });
await apiClient.delete('/users/1');
โฑ๏ธ ํ์์์ ์ฒ๋ฆฌํ๊ธฐ
Fetch API๋ ๊ธฐ๋ณธ์ ์ผ๋ก ํ์์์ ๊ธฐ๋ฅ์ด ์์ด. ์๋ฒ๊ฐ ์๋ต์ ์ ํ๋ฉด ์์ํ ๊ธฐ๋ค๋ฆด ์๋ ์์ด ๐ฑ AbortController๋ฅผ ์ฌ์ฉํด์ ํ์์์์ ๊ตฌํํด๋ณด์!
// AbortController๋ก ํ์์์ ๊ตฌํ
async function fetchWithTimeout(url, options = {}, timeout = 5000) {
const controller = new AbortController();
// ์ง์ ๋ ์๊ฐ ํ ์์ฒญ ์ทจ์
const timeoutId = setTimeout(() => {
controller.abort();
}, timeout);
try {
const response = await fetch(url, {
...options,
signal: controller.signal // AbortController ์ฐ๊ฒฐ
});
clearTimeout(timeoutId); // ์ฑ๊ณตํ๋ฉด ํ์ด๋จธ ์ทจ์
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return await response.json();
} catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error(`์์ฒญ ํ์์์! ${timeout}ms ์ด๊ณผ`);
}
throw error;
}
}
// 5์ด ์์ ์๋ต ์์ผ๋ฉด ํ์์์
try {
const data = await fetchWithTimeout('https://api.example.com/slow', {}, 5000);
console.log(data);
} catch (error) {
console.error(error.message); // "์์ฒญ ํ์์์! 5000ms ์ด๊ณผ"
}
๐ ์๋ ์ฌ์๋(Retry) ๋ก์ง
๋คํธ์ํฌ๊ฐ ๋ถ์์ ํ ๋ ์๋์ผ๋ก ์ฌ์๋ํ๋ ๋ก์ง๋ ์ค์ ์์ ๋ง์ด ์จ!
// ์๋ ์ฌ์๋ fetch
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
let lastError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await fetch(url, options);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return await response.json();
} catch (error) {
lastError = error;
console.log(`์๋ ${attempt}/${maxRetries} ์คํจ: ${error.message}`);
if (attempt < maxRetries) {
// ์ง์ ๋ฐฑ์คํ: 1์ด, 2์ด, 4์ด... ์ ์ ๋์ด๋จ
const delay = Math.pow(2, attempt - 1) * 1000;
console.log(`${delay}ms ํ ์ฌ์๋...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
throw new Error(`${maxRetries}๋ฒ ์๋ ํ ์คํจ: ${lastError.message}`);
}
// ์ฌ์ฉ
try {
const data = await fetchWithRetry('https://api.example.com/data');
console.log('์ฑ๊ณต:', data);
} catch (error) {
console.error('์ต์ข
์คํจ:', error.message);
}
โก ์ฌ๋ฌ ์์ฒญ ๋์์ ์ฒ๋ฆฌํ๊ธฐ โ Promise.all
์ฌ๋ฌ API๋ฅผ ์์๋๋ก ํธ์ถํ๋ฉด ์๊ฐ์ด ์ค๋ ๊ฑธ๋ ค. ์๋ก ์์กด์ฑ์ด ์๋ ์์ฒญ๋ค์ ๋์์ ๋ณด๋ด๋ฉด ํจ์ฌ ๋น ๋ฅด์ง! ์ด๋ Promise.all()์ ์จ.
// โ ๋นํจ์จ์ ์ธ ๋ฐฉ๋ฒ โ ์์๋๋ก ๊ธฐ๋ค๋ฆผ (์ด 3์ด ์์)
const users = await fetch('/api/users').then(r => r.json()); // 1์ด
const posts = await fetch('/api/posts').then(r => r.json()); // 1์ด
const comments = await fetch('/api/comments').then(r => r.json()); // 1์ด
// โ
ํจ์จ์ ์ธ ๋ฐฉ๋ฒ โ ๋์์ ์์ฒญ (์ด 1์ด ์์!)
const [users, posts, comments] = await Promise.all([
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
fetch('/api/comments').then(r => r.json())
]);
console.log('์ ์ :', users);
console.log('๊ฒ์๊ธ:', posts);
console.log('๋๊ธ:', comments);
Promise.allSettled()๋ฅผ ์จ!
// Promise.allSettled โ ์ผ๋ถ ์คํจํด๋ ๋๋จธ์ง ๊ฒฐ๊ณผ ๋ฐ๊ธฐ
const results = await Promise.allSettled([
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
fetch('/api/broken-endpoint').then(r => r.json()) // ์ด๊ฒ ์คํจํด๋ OK
]);
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`์์ฒญ ${index + 1} ์ฑ๊ณต:`, result.value);
} else {
console.log(`์์ฒญ ${index + 1} ์คํจ:`, result.reason);
}
});
๐ง CORS โ ๊ฐ๋ฐ์์ ์์ํ ์น๊ตฌ(์ด์ ์ )
Fetch๋ฅผ ์ฐ๋ค ๋ณด๋ฉด ๋ฐ๋์ ๋ง๋๊ฒ ๋๋ ๋ ์์ด ์์ด. ๋ฐ๋ก CORS(Cross-Origin Resource Sharing)์ผ. ์ฝ์์ ๋นจ๊ฐ ์๋ฌ ๋ฉ์์ง๊ฐ ๋จ๋ฉด์ "CORS policy" ์ด์ฉ๊ณ ํ๋ ๊ฑฐ ๋ณธ ์ ์์ง? ๐
ํ๋กํ ์ฝ + ๋๋ฉ์ธ + ํฌํธ์ ์กฐํฉ์ด์ผ.์๋ฅผ ๋ค์ด
https://mysite.com์์ https://api.other.com์ผ๋ก ์์ฒญํ๋ฉด CORS ์๋ฌ๊ฐ ๋ฐ์ํด.
// CORS ์๋ฌ๊ฐ ๋๋ ์ํฉ
// ๋ด ์ฌ์ดํธ: https://mysite.com
// API ์๋ฒ: https://api.other.com
fetch('https://api.other.com/data')
// โ CORS ์๋ฌ! ๋ธ๋ผ์ฐ์ ๊ฐ ๋ง์
// ํด๊ฒฐ์ฑ
1: ์๋ฒ์์ CORS ํค๋ ์ค์ (๊ฐ์ฅ ์ข์ ๋ฐฉ๋ฒ)
// ์๋ฒ ์๋ต ํค๋์ ์ด๊ฒ ์์ด์ผ ํด:
// Access-Control-Allow-Origin: https://mysite.com
// ๋๋
// Access-Control-Allow-Origin: * (๋ชจ๋ ์ถ์ฒ ํ์ฉ)
// ํด๊ฒฐ์ฑ
2: ํ๋ก์ ์๋ฒ ์ฌ์ฉ (๊ฐ๋ฐ ํ๊ฒฝ)
// vite.config.js ๋๋ webpack.config.js์์ ํ๋ก์ ์ค์
// server: {
// proxy: {
// '/api': 'https://api.other.com'
// }
// }
// ํด๊ฒฐ์ฑ
3: CORS ๋ชจ๋ ์ค์
fetch('https://api.other.com/data', {
mode: 'cors', // ๊ธฐ๋ณธ๊ฐ, CORS ํ์ฉ ์์ฒญ
// mode: 'no-cors', // CORS ์์ด (์๋ต ์ฝ๊ธฐ ๋ถ๊ฐ)
// mode: 'same-origin' // ๊ฐ์ ์ถ์ฒ๋ง ํ์ฉ
});
๐ ํ์ผ ์ ๋ก๋ โ FormData ํ์ฉ
์ด๋ฏธ์ง๋ ํ์ผ์ ์๋ฒ์ ์ฌ๋ฆด ๋๋ FormData๋ฅผ ์ฌ์ฉํด. JSON์ด ์๋๋ผ multipart/form-data ํ์์ผ๋ก ์ ์กํด์ผ ํด.
// ํ์ผ ์
๋ก๋ ์์
async function uploadFile(file) {
const formData = new FormData();
formData.append('file', file); // ํ์ผ ์ถ๊ฐ
formData.append('description', 'ํ๋กํ ์ด๋ฏธ์ง'); // ์ถ๊ฐ ๋ฐ์ดํฐ
formData.append('userId', '12345');
try {
const response = await fetch('https://api.example.com/upload', {
method: 'POST',
// Content-Type ํค๋๋ฅผ ์ง์ ์ค์ ํ์ง ๋ง!
// FormData ์ฌ์ฉ ์ ๋ธ๋ผ์ฐ์ ๊ฐ ์๋์ผ๋ก ์ค์ ํด์ค
body: formData
});
if (!response.ok) throw new Error('์
๋ก๋ ์คํจ');
const result = await response.json();
console.log('์
๋ก๋ ์๋ฃ! URL:', result.fileUrl);
} catch (error) {
console.error('์
๋ก๋ ์๋ฌ:', error);
}
}
// input[type="file"]์์ ํ์ผ ๊ฐ์ ธ์ค๊ธฐ
const fileInput = document.querySelector('#fileInput');
fileInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (file) uploadFile(file);
});
Content-Type ํค๋๋ฅผ ์ง์ ์ค์ ํ๋ฉด ์ ๋ผ. ๋ธ๋ผ์ฐ์ ๊ฐ ์๋์ผ๋ก multipart/form-data; boundary=... ํ์์ผ๋ก ์ค์ ํด์ค. ์ง์ ์ค์ ํ๋ฉด boundary ๊ฐ์ด ๋น ์ ธ์ ์๋ฒ๊ฐ ํ์ผ์ ๋ชป ์ฝ์ด!
โ๏ธ XHR vs Fetch API โ ๋ญ ์จ์ผ ํด?
โ ์ค๋๋ ๋ธ๋ผ์ฐ์ ์ง์
โ ์ ๋ก๋ ์งํ๋ฅ ์ถ์ ๊ฐ๋ฅ
โ ์์ฒญ ์ทจ์ ๊ฐ๋ฅ
โ ์ฝ๋๊ฐ ๋ณต์กํ๊ณ ์ฅํฉ
โ Promise ๋ฏธ์ง์ (์ฝ๋ฐฑ ๊ธฐ๋ฐ)
โ ์๋ฌ ์ฒ๋ฆฌ ๋ฒ๊ฑฐ๋ก์
โ ๊น๋ํ๊ณ ์ง๊ด์ ์ธ ์ฝ๋
โ Promise/async-await ์ง์
โ AbortController๋ก ์ทจ์ ๊ฐ๋ฅ
โ ์คํธ๋ฆฌ๋ฐ ์ง์
โ IE ๋ฏธ์ง์ (IE๋ ์ด์ ์ฃฝ์์ผ๋ OK)
โ ์ ๋ก๋ ์งํ๋ฅ ์ถ์ ์ด๋ ค์
๐ฏ ์ค์ ์์ โ ๊ฒ์ ์๋์์ฑ ๊ตฌํ
์ด๋ก ์ ์ถฉ๋ถํ ๋ฐฐ์ ์ผ๋, ์ค์ ๋ก ๋ง์ด ์ฐ์ด๋ ๊ฒ์ ์๋์์ฑ ๊ธฐ๋ฅ์ ๋ง๋ค์ด๋ณด์! ์ฌ์ฉ์๊ฐ ํ์ดํํ ๋๋ง๋ค API๋ฅผ ํธ์ถํด์ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์ฌ์ฃผ๋ ๊ฑฐ์ผ.
// ๊ฒ์ ์๋์์ฑ ๊ตฌํ
// ์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์ ์ฌ๋ฅ ๊ฒ์ ๊ธฐ๋ฅ์ ๋ง๋ ๋ค๊ณ ๊ฐ์ ํด๋ณด์!
// ๋๋ฐ์ด์ค ํจ์ โ ํ์ดํ ๋ฉ์ถ ํ 300ms ๋ค์ API ํธ์ถ
function debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(this, args), delay);
};
}
// ๊ฒ์ API ํธ์ถ
async function searchTalents(query) {
if (!query.trim()) {
clearResults();
return;
}
const controller = new AbortController();
try {
showLoading(); // ๋ก๋ฉ ํ์
const response = await fetch(
`https://api.example.com/talents/search?q=${encodeURIComponent(query)}&limit=10`,
{ signal: controller.signal }
);
if (!response.ok) throw new Error('๊ฒ์ ์คํจ');
const { results } = await response.json();
displayResults(results);
} catch (error) {
if (error.name !== 'AbortError') {
console.error('๊ฒ์ ์๋ฌ:', error);
showError('๊ฒ์ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.');
}
} finally {
hideLoading(); // ๋ก๋ฉ ์จ๊ธฐ๊ธฐ
}
}
// ๊ฒฐ๊ณผ ํ์
function displayResults(results) {
const container = document.querySelector('#search-results');
container.innerHTML = '';
if (results.length === 0) {
container.innerHTML = '<p>๊ฒ์ ๊ฒฐ๊ณผ๊ฐ ์์ต๋๋ค.</p>';
return;
}
results.forEach(talent => {
const item = document.createElement('div');
item.className = 'result-item';
item.textContent = `${talent.name} โ ${talent.category}`;
item.addEventListener('click', () => selectTalent(talent));
container.appendChild(item);
});
}
// ๋๋ฐ์ด์ค ์ ์ฉ๋ ๊ฒ์ ํจ์
const debouncedSearch = debounce(searchTalents, 300);
// ์ด๋ฒคํธ ๋ฆฌ์ค๋
const searchInput = document.querySelector('#search-input');
searchInput.addEventListener('input', (e) => {
debouncedSearch(e.target.value);
});
โข AbortController: ์ ๊ฒ์์ด ์ ๋ ฅ ์ ์ด์ ์์ฒญ ์ทจ์.
โข encodeURIComponent: URL์ ํ๊ธ์ด๋ ํน์๋ฌธ์ ํฌํจ ์ ์ธ์ฝ๋ฉ ํ์!
๐ฆ ๋ค์ํ ์๋ต ํ์ ์ฒ๋ฆฌํ๊ธฐ
์๋ฒ๊ฐ ํญ์ JSON๋ง ๋ฐํํ๋ ๊ฑด ์๋์ผ. ํ ์คํธ, ์ด๋ฏธ์ง, ํ์ผ ๋ฑ ๋ค์ํ ํ์์ ์ฒ๋ฆฌํ ์ ์์ด์ผ ํด.
// ๋ค์ํ ์๋ต ํ์ ์ฒ๋ฆฌ
// 1. JSON (๊ฐ์ฅ ํํจ)
const jsonData = await fetch('/api/data').then(r => r.json());
// 2. ํ
์คํธ
const textData = await fetch('/api/text').then(r => r.text());
// 3. Blob (์ด๋ฏธ์ง, ํ์ผ ๋ค์ด๋ก๋)
const blob = await fetch('/api/image').then(r => r.blob());
const imageUrl = URL.createObjectURL(blob);
document.querySelector('img').src = imageUrl;
// 4. ArrayBuffer (๋ฐ์ด๋๋ฆฌ ๋ฐ์ดํฐ)
const buffer = await fetch('/api/binary').then(r => r.arrayBuffer());
// 5. FormData
const formData = await fetch('/api/form').then(r => r.formData());
// 6. ํ์ผ ๋ค์ด๋ก๋ ๊ตฌํ
async function downloadFile(url, filename) {
const response = await fetch(url);
const blob = await response.blob();
// ๋ค์ด๋ก๋ ๋งํฌ ์์ฑ
const downloadUrl = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = downloadUrl;
link.download = filename;
link.click();
// ๋ฉ๋ชจ๋ฆฌ ํด์
URL.revokeObjectURL(downloadUrl);
}
downloadFile('/api/report.pdf', '๋ณด๊ณ ์.pdf');
๐ก๏ธ ๋ณด์ โ ์ ๋ ๋์น๋ฉด ์ ๋๋ ๊ฒ๋ค
๋น๋๊ธฐ ํต์ ์ ๊ตฌํํ ๋ ๋ณด์๋ ๊ผญ ์ฑ๊ฒจ์ผ ํด. ํนํ ์ด ์ธ ๊ฐ์ง๋ ํ์์ผ!
API ํค, ๋น๋ฐ๋ฒํธ, ํ ํฐ์ URL ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ์ ๋ฃ์ผ๋ฉด ์๋ฒ ๋ก๊ทธ์ ๋จ์. ๋ฐ๋์ ํค๋๋ ์์ฒญ ๋ณธ๋ฌธ(body)์ ๋ด์์ ๋ณด๋ด.
HTTP๋ก ํต์ ํ๋ฉด ์ค๊ฐ์์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ๋ก์ฑ ์ ์์ด. ์ค์ ์๋น์ค์์๋ ๋ฐ๋์ HTTPS๋ฅผ ์จ์ผ ํด.
์๋ฒ์์ ๋ฐ์ ๋ฐ์ดํฐ๋ฅผ
innerHTML๋ก ์ง์ ์ฝ์
ํ๋ฉด XSS ๊ณต๊ฒฉ์ ์ทจ์ฝํด. textContent๋ฅผ ์ฐ๊ฑฐ๋ DOMPurify ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก ์ ์ ํด.
// โ ์ํํ ์ฝ๋ โ XSS ์ทจ์ฝ์
const data = await fetch('/api/comment').then(r => r.json());
document.querySelector('#comment').innerHTML = data.content;
// data.content๊ฐ "<script>์
์ฑ์ฝ๋</script>" ๋ผ๋ฉด?? ๐ฑ
// โ
์์ ํ ์ฝ๋
document.querySelector('#comment').textContent = data.content;
// textContent๋ HTML์ ํ์ฑํ์ง ์์์ ์์ ํด!
๐ ์ ๋ฆฌ โ ์ค๋ ๋ฐฐ์ด ๊ฒ๋ค
์, ์ ๋ง ๋ง์ ๊ฑธ ๋ค๋ค์ง? ๐ ์ค๋ ๋ฐฐ์ด ๋ด์ฉ์ ํ๋ฒ ์ ๋ฆฌํด๋ณด์!
ํน์ ๋ ๊น์ด ๊ณต๋ถํ๊ณ ์ถ๋ค๋ฉด Axios(Fetch๋ฅผ ๋ ํธํ๊ฒ ์ธ ์ ์๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ), SWR์ด๋ React Query(๋ฐ์ดํฐ ํ์นญ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ), WebSocket(์ค์๊ฐ ์๋ฐฉํฅ ํต์ ) ๊ฐ์ ์ฃผ์ ๋ค๋ ์ฐพ์๋ด. ๋น๋๊ธฐ ํต์ ์ ์ธ๊ณ๋ ์ ๋ง ๋๊ณ ๊น๊ฑฐ๋ ! ๐
๊ทธ๋ฆฌ๊ณ ๋ค์ํ ๊ฐ๋ฐ ์ง์์ ๊ณต์ ํ๊ณ ์ถ๋ค๋ฉด ์ฌ๋ฅ๋ท์ ์ง์์ธ์ ์ฒ ๊ฐ์ ๊ณต๊ฐ์ ํ์ฉํด๋ด. ๋์ฒ๋ผ ์๋ ๊ฑธ ๊ธ๋ก ์ ๋ฆฌํ๋ค ๋ณด๋ฉด ์ค๋ ฅ์ด ํจ์ฌ ๋นจ๋ฆฌ ๋๊ฑฐ๋ ! ๐
๊ด๋ จ ํค์๋
๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์์ง ๋๊ธ์ด ์์ต๋๋ค.