์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐ŸŒ AJAX์™€ Fetch API๋ฅผ ํ™œ์šฉํ•œ๋น„๋™๊ธฐ ๋ฐ์ดํ„ฐ ํ†ต์‹ 
JavaScript ยท ํ”„๋กœ๊ทธ๋žจ๊ฐœ๋ฐœ

๐ŸŒ AJAX์™€ Fetch API๋ฅผ ํ™œ์šฉํ•œ
๋น„๋™๊ธฐ ๋ฐ์ดํ„ฐ ํ†ต์‹ 

์„œ๋ฒ„๋ž‘ ๋ชฐ๋ž˜ ๋Œ€ํ™”ํ•˜๋Š” ๋ฒ• โ€” ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ ์—†์ด ๋ฐ์ดํ„ฐ ์ฃผ๊ณ ๋ฐ›๊ธฐ ์™„์ „ ์ •๋ณต ๐Ÿš€

์•ผ, ์†”์งํžˆ ๋งํ•ด๋ด. ์›น์‚ฌ์ดํŠธ ์“ฐ๋‹ค๊ฐ€ ๋ฒ„ํŠผ ํ•˜๋‚˜ ๋ˆŒ๋ €๋Š”๋ฐ ํŽ˜์ด์ง€ ์ „์ฒด๊ฐ€ ํ•˜์–—๊ฒŒ ๊นœ๋นก์ด๋ฉด์„œ ์ƒˆ๋กœ๊ณ ์นจ๋˜๋Š” ๊ฑฐ ์ง„์งœ ๋‹ต๋‹ตํ•˜์ง€ ์•Š์•„? ๐Ÿ˜ค
๊ทธ๋Ÿฐ๋ฐ ์š”์ฆ˜ ์œ ํŠœ๋ธŒ, ์ธ์Šคํƒ€, ์นด์นด์˜คํ†ก ์›น๋ฒ„์ „ ๊ฐ™์€ ์„œ๋น„์Šค๋“ค์€ ๋ฒ„ํŠผ ๋ˆŒ๋Ÿฌ๋„ ํŽ˜์ด์ง€๊ฐ€ ์ „ํ˜€ ์•ˆ ๊นœ๋นก์ด์ž–์•„. ๋Œ“๊ธ€ ๋‹ฌ์•„๋„, ์ข‹์•„์š” ๋ˆŒ๋Ÿฌ๋„, ๊ฒ€์ƒ‰ํ•ด๋„ โ€” ๊ทธ๋ƒฅ ์Šค๋ฅด๋ฅต ๋ฐ์ดํ„ฐ๊ฐ€ ๋ฐ”๋€Œ์ง€.

์ด๊ฒŒ ๋ฐ”๋กœ ๋น„๋™๊ธฐ ๋ฐ์ดํ„ฐ ํ†ต์‹ ์˜ ๋งˆ๋ฒ•์ด์•ผ. ๊ทธ๋ฆฌ๊ณ  ๊ทธ ๋งˆ๋ฒ•์˜ ํ•ต์‹ฌ ์ฃผ๋ฌธ์ด ๋ฐ”๋กœ AJAX์™€ Fetch API์•ผ. ์˜ค๋Š˜์€ ์ด ๋‘ ๋…€์„์„ ์™„์ „ํžˆ ํŒŒํ—ค์ณ๋ณผ ๊ฑฐ์•ผ. ์–ด๋ ต๊ฒŒ ์ƒ๊ฐํ•˜์ง€ ๋งˆ โ€” ์นœ๊ตฌํ•œํ…Œ ์„ค๋ช…ํ•˜๋“ฏ์ด ์‰ฝ๊ณ  ์žฌ๋ฏธ์žˆ๊ฒŒ ํ’€์–ด์ค„๊ฒŒ! ๐ŸŽ‰

๋™๊ธฐ ๋ฐฉ์‹ vs ๋น„๋™๊ธฐ ๋ฐฉ์‹ ๐Ÿ˜ฉ ๋™๊ธฐ(Synchronous) ๋ฐฉ์‹ ๋ธŒ๋ผ์šฐ์ € ์š”์ฒญ ์ „์†ก ๐Ÿ“ค โณ ๊ธฐ๋‹ค๋ฆฌ๋Š” ์ค‘... ์•„๋ฌด๊ฒƒ๋„ ๋ชป ํ•จ ํ™”๋ฉด ๋ฉˆ์ถค ๐Ÿ˜ต ์‘๋‹ต ์ˆ˜์‹  ๐Ÿ“ฅ ํ™”๋ฉด ๊ฐฑ์‹  ๐Ÿ”„ ์„œ๋ฒ„๋กœ ์„œ๋ฒ„์—์„œ ๐Ÿ˜Ž ๋น„๋™๊ธฐ(Asynchronous) ๋ฐฉ์‹ ๋ธŒ๋ผ์šฐ์ € ์š”์ฒญ ์ „์†ก ๐Ÿ“ค ๋‹ค๋ฅธ ์ž‘์—… ๊ณ„์† โœ… UI ๋ฐ˜์‘ ์œ ์ง€ โœ… ์‘๋‹ต ์ˆ˜์‹  ๐Ÿ“ฅ ํ™”๋ฉด ๋ถ€๋ถ„ ๊ฐฑ์‹  โœจ ์„œ๋ฒ„๋กœ ์„œ๋ฒ„์—์„œ ์„œ๋ฒ„ ์„œ๋ฒ„

๐Ÿ•ฐ๏ธ AJAX๊ฐ€ ๋ญ”๋ฐ? ์—ญ์‚ฌ๋ถ€ํ„ฐ ์งš๊ณ  ๊ฐ€์ž

AJAX๋Š” Asynchronous JavaScript And XML์˜ ์•ฝ์ž์•ผ. ์ด๋ฆ„๋งŒ ๋ณด๋ฉด XML์ด ํ•ต์‹ฌ์ธ ๊ฒƒ ๊ฐ™์ง€๋งŒ, ์š”์ฆ˜์€ XML๋ณด๋‹ค JSON์„ ํ›จ์”ฌ ๋งŽ์ด ์จ. ์ด๋ฆ„์ด ์ข€ ๊ตฌ์‹์ด๊ธด ํ•œ๋ฐ, ๊ฐœ๋… ์ž์ฒด๋Š” ์—ฌ์ „ํžˆ ํ˜„์—ญ์ด์•ผ! ๐Ÿ˜„

๐Ÿ’ก AJAX์˜ ํ•ต์‹ฌ ์•„์ด๋””์–ด: ํŽ˜์ด์ง€ ์ „์ฒด๋ฅผ ์ƒˆ๋กœ๊ณ ์นจํ•˜์ง€ ์•Š๊ณ , ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์„œ๋ฒ„์™€ ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ๊ณ ๋ฐ›๋Š” ๊ฒƒ. ์‚ฌ์šฉ์ž๋Š” ๊ณ„์† ํŽ˜์ด์ง€๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ณ , ํ•„์š”ํ•œ ๋ถ€๋ถ„๋งŒ ์—…๋ฐ์ดํŠธ๋ผ.

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 โœ… ์™„๋ฃŒ! ์ด๋•Œ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌํ•˜๋ฉด ๋ผ
โš ๏ธ XHR์˜ ๋‹จ์ : ์ฝ”๋“œ๊ฐ€ ์žฅํ™ฉํ•˜๊ณ , ์ฝœ๋ฐฑ ์ค‘์ฒฉ์ด ์‹ฌํ•ด์ง€๋ฉด "์ฝœ๋ฐฑ ์ง€์˜ฅ(Callback Hell)"์ด ๋ฐœ์ƒํ•ด. ์—๋Ÿฌ ์ฒ˜๋ฆฌ๋„ ๋ฒˆ๊ฑฐ๋กญ๊ณ , ์ฝ”๋“œ ๊ฐ€๋…์„ฑ์ด ๋–จ์–ด์ ธ. ๊ทธ๋ž˜์„œ ๋“ฑ์žฅํ•œ ๊ฒŒ ๋ฐ”๋กœ Fetch API์•ผ!

โœจ Fetch API โ€” ํ˜„๋Œ€์ ์ด๊ณ  ์„ธ๋ จ๋œ ๋ฐฉ์‹

Fetch API๋Š” 2015๋…„์— ๋“ฑ์žฅํ•œ ํ˜„๋Œ€์ ์ธ ๋น„๋™๊ธฐ ํ†ต์‹  ๋ฐฉ๋ฒ•์ด์•ผ. XHR์˜ ๋ณต์žกํ•จ์„ ์‹น ๊ฑท์–ด๋‚ด๊ณ , Promise ๊ธฐ๋ฐ˜์œผ๋กœ ๋งŒ๋“ค์–ด์กŒ์–ด. ์ฝ”๋“œ๊ฐ€ ํ›จ์”ฌ ๊น”๋”ํ•˜๊ณ  ์ฝ๊ธฐ ์‰ฌ์›Œ์กŒ์ง€.

๐ŸŽฏ Fetch API์˜ ํŠน์ง•: โ€ข Promise ๊ธฐ๋ฐ˜ โ†’ async/await์™€ ์ฐฐ๋–ก๊ถํ•ฉ
โ€ข ๊ธฐ๋ณธ ๋‚ด์žฅ โ†’ ๋ณ„๋„ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ค์น˜ ๋ถˆํ•„์š”
โ€ข ์ŠคํŠธ๋ฆฌ๋ฐ ์ง€์›, ์š”์ฒญ/์‘๋‹ต ๊ฐ์ฒด ๋ถ„๋ฆฌ
โ€ข ์ฝ”๋“œ๊ฐ€ ์ง๊ด€์ ์ด๊ณ  ์ฝ๊ธฐ ์‰ฌ์›€

๐Ÿ”ฐ 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ํ•˜์ง€ ์•Š์•„. ์ด๊ฒŒ ์ดˆ๋ณด์ž๋“ค์ด ์ž์ฃผ ์‹ค์ˆ˜ํ•˜๋Š” ๋ถ€๋ถ„์ด์•ผ!

โš ๏ธ Fetch์˜ ํ•จ์ •! ์„œ๋ฒ„๊ฐ€ 404๋‚˜ 500 ์—๋Ÿฌ๋ฅผ ๋ฐ˜ํ™˜ํ•ด๋„ .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();
๐Ÿ’š ํŒ: async/await๋Š” ES2017(ES8)๋ถ€ํ„ฐ ์ง€์›๋ผ. ํ˜„์žฌ ๋ชจ๋“  ๋ชจ๋˜ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ง€์›ํ•˜๋‹ˆ๊นŒ ๋งˆ์Œ๊ป ์จ๋„ ๋ผ!
Fetch API ๋™์ž‘ ํ๋ฆ„ โ‘  fetch() ํ˜ธ์ถœ Promise ๋ฐ˜ํ™˜ โ‘ก ๋„คํŠธ์›Œํฌ ์š”์ฒญ ์ „์†ก ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์ฒ˜๋ฆฌ โ‘ข Response ๊ฐ์ฒด ์ˆ˜์‹  ํ—ค๋” ์ •๋ณด, status, ok ํฌํ•จ response.ok = false โŒ response.ok = true โœ… ์—๋Ÿฌ throw .catch()๋กœ ์ฒ˜๋ฆฌ response.json() ๋ฐ์ดํ„ฐ ํŒŒ์‹ฑ โœ… ๋ฐ์ดํ„ฐ ํ™œ์šฉ! .then() ์‹คํ–‰ โŒ ์—๋Ÿฌ ์ฒ˜๋ฆฌ .catch() ์‹คํ–‰

๐Ÿ“ฌ 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 ๋ฐ์ดํ„ฐ ์‚ญ์ œ โŒ โœ… ์žˆ์Œ
๐Ÿ’š ๋ฉฑ๋“ฑ์„ฑ(Idempotency)์ด๋ž€? ๊ฐ™์€ ์š”์ฒญ์„ ์—ฌ๋Ÿฌ ๋ฒˆ ํ•ด๋„ ๊ฒฐ๊ณผ๊ฐ€ ๋™์ผํ•œ ์„ฑ์งˆ์ด์•ผ. GET์œผ๋กœ ๊ฐ™์€ URL์„ 100๋ฒˆ ์š”์ฒญํ•ด๋„ ๋ฐ์ดํ„ฐ๋Š” ๋ณ€ํ•˜์ง€ ์•Š์ž–์•„? ๊ทธ๊ฒŒ ๋ฉฑ๋“ฑ์„ฑ์ด์•ผ. POST๋Š” ๋ˆ„๋ฅผ ๋•Œ๋งˆ๋‹ค ์ƒˆ ๋ฐ์ดํ„ฐ๊ฐ€ ์ƒ๊ธฐ๋‹ˆ๊นŒ ๋ฉฑ๋“ฑ์„ฑ์ด ์—†์–ด.

๐Ÿ” ํ—ค๋”(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.all์˜ ์ฃผ์˜์ : ํ•˜๋‚˜๋ผ๋„ ์‹คํŒจํ•˜๋ฉด ์ „์ฒด๊ฐ€ reject๋ผ. ์ผ๋ถ€ ์‹คํŒจ๋ฅผ ํ—ˆ์šฉํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด 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);
  }
});
์ˆœ์ฐจ ์š”์ฒญ vs ๋ณ‘๋ ฌ ์š”์ฒญ(Promise.all) ์‹œ๊ฐ„ ๋น„๊ต ๐Ÿ˜ฉ ์ˆœ์ฐจ ์š”์ฒญ (์ด 3์ดˆ) ์œ ์ € ์š”์ฒญ (1์ดˆ) ๊ฒŒ์‹œ๊ธ€ ์š”์ฒญ (1์ดˆ) ๋Œ“๊ธ€ ์š”์ฒญ (1์ดˆ) ์™„๋ฃŒ 3์ดˆ 0์ดˆ 1์ดˆ 2์ดˆ 3์ดˆ ๐Ÿ˜Ž ๋ณ‘๋ ฌ ์š”์ฒญ Promise.all (์ด 1์ดˆ!) ์œ ์ € ์š”์ฒญ (1์ดˆ) ๊ฒŒ์‹œ๊ธ€ ์š”์ฒญ (1์ดˆ) ๋Œ“๊ธ€ ์š”์ฒญ (1์ดˆ) ์™„๋ฃŒ 1์ดˆ! ๐ŸŽ‰ โšก 2์ดˆ ์ ˆ์•ฝ! ์ˆœ์ฐจ ๋Œ€๋น„ 3๋ฐฐ ๋น ๋ฆ„ Promise.all์˜ ํž˜ ๐Ÿ’ช

๐Ÿšง CORS โ€” ๊ฐœ๋ฐœ์ž์˜ ์˜์›ํ•œ ์นœ๊ตฌ(์ด์ž ์ )

Fetch๋ฅผ ์“ฐ๋‹ค ๋ณด๋ฉด ๋ฐ˜๋“œ์‹œ ๋งŒ๋‚˜๊ฒŒ ๋˜๋Š” ๋…€์„์ด ์žˆ์–ด. ๋ฐ”๋กœ CORS(Cross-Origin Resource Sharing)์•ผ. ์ฝ˜์†”์— ๋นจ๊ฐ„ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๊ฐ€ ๋œจ๋ฉด์„œ "CORS policy" ์–ด์ฉŒ๊ณ  ํ•˜๋Š” ๊ฑฐ ๋ณธ ์  ์žˆ์ง€? ๐Ÿ˜…

๐ŸŒ CORS๋ž€? ๋ธŒ๋ผ์šฐ์ €์˜ ๋ณด์•ˆ ์ •์ฑ…์ด์•ผ. ๊ธฐ๋ณธ์ ์œผ๋กœ ๋‹ค๋ฅธ ์ถœ์ฒ˜(Origin)์˜ ๋ฆฌ์†Œ์Šค์— ์ ‘๊ทผํ•˜๋Š” ๊ฑธ ๋ง‰์•„. ์ถœ์ฒ˜๋Š” ํ”„๋กœํ† ์ฝœ + ๋„๋ฉ”์ธ + ํฌํŠธ์˜ ์กฐํ•ฉ์ด์•ผ.

์˜ˆ๋ฅผ ๋“ค์–ด 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' // ๊ฐ™์€ ์ถœ์ฒ˜๋งŒ ํ—ˆ์šฉ
});
๐Ÿ’š ๊ฐœ๋ฐœ ํŒ: ๊ฐœ๋ฐœ ์ค‘์— CORS ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธฐ๋ฉด ๋ณดํ†ต ํ”„๋ก์‹œ ์„ค์ •์œผ๋กœ ํ•ด๊ฒฐํ•ด. ์‹ค์ œ ๋ฐฐํฌ ํ™˜๊ฒฝ์—์„œ๋Š” ์„œ๋ฒ„์—์„œ ์˜ฌ๋ฐ”๋ฅธ CORS ํ—ค๋”๋ฅผ ์„ค์ •ํ•˜๋Š” ๊ฒŒ ์ •์„์ด์•ผ. ๋ธŒ๋ผ์šฐ์ € ํ™•์žฅ ํ”„๋กœ๊ทธ๋žจ์œผ๋กœ CORS๋ฅผ ์šฐํšŒํ•˜๋Š” ๊ฑด ๊ฐœ๋ฐœ ํ…Œ์ŠคํŠธ์šฉ์œผ๋กœ๋งŒ ์จ์•ผ ํ•ด!

๐Ÿ“ ํŒŒ์ผ ์—…๋กœ๋“œ โ€” 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);
});
โš ๏ธ ์ค‘์š”! FormData๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ๋Š” Content-Type ํ—ค๋”๋ฅผ ์ง์ ‘ ์„ค์ •ํ•˜๋ฉด ์•ˆ ๋ผ. ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ž๋™์œผ๋กœ multipart/form-data; boundary=... ํ˜•์‹์œผ๋กœ ์„ค์ •ํ•ด์ค˜. ์ง์ ‘ ์„ค์ •ํ•˜๋ฉด boundary ๊ฐ’์ด ๋น ์ ธ์„œ ์„œ๋ฒ„๊ฐ€ ํŒŒ์ผ์„ ๋ชป ์ฝ์–ด!

โš–๏ธ XHR vs Fetch API โ€” ๋ญ˜ ์จ์•ผ ํ•ด?

๐Ÿ•ฐ๏ธ XMLHttpRequest

โœ… ์˜ค๋ž˜๋œ ๋ธŒ๋ผ์šฐ์ € ์ง€์›
โœ… ์—…๋กœ๋“œ ์ง„ํ–‰๋ฅ  ์ถ”์  ๊ฐ€๋Šฅ
โœ… ์š”์ฒญ ์ทจ์†Œ ๊ฐ€๋Šฅ
โŒ ์ฝ”๋“œ๊ฐ€ ๋ณต์žกํ•˜๊ณ  ์žฅํ™ฉ
โŒ Promise ๋ฏธ์ง€์› (์ฝœ๋ฐฑ ๊ธฐ๋ฐ˜)
โŒ ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๋ฒˆ๊ฑฐ๋กœ์›€
โœจ Fetch API

โœ… ๊น”๋”ํ•˜๊ณ  ์ง๊ด€์ ์ธ ์ฝ”๋“œ
โœ… Promise/async-await ์ง€์›
โœ… AbortController๋กœ ์ทจ์†Œ ๊ฐ€๋Šฅ
โœ… ์ŠคํŠธ๋ฆฌ๋ฐ ์ง€์›
โŒ IE ๋ฏธ์ง€์› (IE๋Š” ์ด์ œ ์ฃฝ์—ˆ์œผ๋‹ˆ OK)
โŒ ์—…๋กœ๋“œ ์ง„ํ–‰๋ฅ  ์ถ”์  ์–ด๋ ค์›€
๐Ÿ’š ๊ฒฐ๋ก : ํŠน๋ณ„ํ•œ ์ด์œ ๊ฐ€ ์—†๋‹ค๋ฉด Fetch API + async/await๋ฅผ ์จ. ์ฝ”๋“œ๊ฐ€ ํ›จ์”ฌ ๊น”๋”ํ•˜๊ณ  ์œ ์ง€๋ณด์ˆ˜ํ•˜๊ธฐ ์‰ฌ์›Œ. ์—…๋กœ๋“œ ์ง„ํ–‰๋ฅ ์ด ๊ผญ ํ•„์š”ํ•˜๋‹ค๋ฉด XHR์ด๋‚˜ Axios ๊ฐ™์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๊ณ ๋ คํ•ด๋ด.

๐ŸŽฏ ์‹ค์ „ ์˜ˆ์ œ โ€” ๊ฒ€์ƒ‰ ์ž๋™์™„์„ฑ ๊ตฌํ˜„

์ด๋ก ์€ ์ถฉ๋ถ„ํžˆ ๋ฐฐ์› ์œผ๋‹ˆ, ์‹ค์ œ๋กœ ๋งŽ์ด ์“ฐ์ด๋Š” ๊ฒ€์ƒ‰ ์ž๋™์™„์„ฑ ๊ธฐ๋Šฅ์„ ๋งŒ๋“ค์–ด๋ณด์ž! ์‚ฌ์šฉ์ž๊ฐ€ ํƒ€์ดํ•‘ํ•  ๋•Œ๋งˆ๋‹ค 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);
});
๐ŸŽฏ ํ•ต์‹ฌ ํฌ์ธํŠธ: โ€ข ๋””๋ฐ”์šด์Šค(Debounce): ํƒ€์ดํ•‘ํ•  ๋•Œ๋งˆ๋‹ค API ํ˜ธ์ถœํ•˜๋ฉด ์„œ๋ฒ„ ๋ถ€ํ•˜๊ฐ€ ์‹ฌํ•ด. 300ms ๊ธฐ๋‹ค๋ ธ๋‹ค๊ฐ€ ํ˜ธ์ถœํ•ด.
โ€ข 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');

๐Ÿ›ก๏ธ ๋ณด์•ˆ โ€” ์ ˆ๋Œ€ ๋†“์น˜๋ฉด ์•ˆ ๋˜๋Š” ๊ฒƒ๋“ค

๋น„๋™๊ธฐ ํ†ต์‹ ์„ ๊ตฌํ˜„ํ•  ๋•Œ ๋ณด์•ˆ๋„ ๊ผญ ์ฑ™๊ฒจ์•ผ ํ•ด. ํŠนํžˆ ์ด ์„ธ ๊ฐ€์ง€๋Š” ํ•„์ˆ˜์•ผ!

1
๋ฏผ๊ฐํ•œ ์ •๋ณด๋ฅผ URL์— ๋„ฃ์ง€ ๋งˆ!
API ํ‚ค, ๋น„๋ฐ€๋ฒˆํ˜ธ, ํ† ํฐ์„ URL ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ์— ๋„ฃ์œผ๋ฉด ์„œ๋ฒ„ ๋กœ๊ทธ์— ๋‚จ์•„. ๋ฐ˜๋“œ์‹œ ํ—ค๋”๋‚˜ ์š”์ฒญ ๋ณธ๋ฌธ(body)์— ๋‹ด์•„์„œ ๋ณด๋‚ด.
2
HTTPS๋ฅผ ์‚ฌ์šฉํ•ด!
HTTP๋กœ ํ†ต์‹ ํ•˜๋ฉด ์ค‘๊ฐ„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€๋กœ์ฑŒ ์ˆ˜ ์žˆ์–ด. ์‹ค์ œ ์„œ๋น„์Šค์—์„œ๋Š” ๋ฐ˜๋“œ์‹œ HTTPS๋ฅผ ์จ์•ผ ํ•ด.
3
XSS ๋ฐฉ์ง€ โ€” ์„œ๋ฒ„ ์‘๋‹ต ๋ฐ์ดํ„ฐ๋ฅผ ๊ทธ๋Œ€๋กœ innerHTML์— ๋„ฃ์ง€ ๋งˆ!
์„œ๋ฒ„์—์„œ ๋ฐ›์€ ๋ฐ์ดํ„ฐ๋ฅผ 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์„ ํŒŒ์‹ฑํ•˜์ง€ ์•Š์•„์„œ ์•ˆ์ „ํ•ด!

๐ŸŽ“ ์ •๋ฆฌ โ€” ์˜ค๋Š˜ ๋ฐฐ์šด ๊ฒƒ๋“ค

์™€, ์ •๋ง ๋งŽ์€ ๊ฑธ ๋‹ค๋ค˜์ง€? ๐Ÿ˜„ ์˜ค๋Š˜ ๋ฐฐ์šด ๋‚ด์šฉ์„ ํ•œ๋ฒˆ ์ •๋ฆฌํ•ด๋ณด์ž!

1
AJAX๋Š” ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ ์—†์ด ์„œ๋ฒ„์™€ ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ๊ณ ๋ฐ›๋Š” ๊ธฐ์ˆ ์ด์•ผ. XMLHttpRequest๊ฐ€ ์›์กฐ์ง€๋งŒ ์ฝ”๋“œ๊ฐ€ ๋ณต์žกํ•ด.
2
Fetch API๋Š” Promise ๊ธฐ๋ฐ˜์˜ ํ˜„๋Œ€์ ์ธ ๋ฐฉ์‹์ด์•ผ. async/await์™€ ํ•จ๊ป˜ ์“ฐ๋ฉด ์ฝ”๋“œ๊ฐ€ ๋งค์šฐ ๊น”๋”ํ•ด์ ธ.
3
HTTP ๋ฉ”์„œ๋“œ๋ฅผ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์‚ฌ์šฉํ•ด์•ผ ํ•ด. GET(์กฐํšŒ), POST(์ƒ์„ฑ), PUT/PATCH(์ˆ˜์ •), DELETE(์‚ญ์ œ).
4
์—๋Ÿฌ ์ฒ˜๋ฆฌ๋Š” ํ•„์ˆ˜์•ผ. response.ok ํ™•์ธ, try/catch, ํƒ€์ž„์•„์›ƒ, ์žฌ์‹œ๋„ ๋กœ์ง์„ ์ ์ ˆํžˆ ํ™œ์šฉํ•ด.
5
Promise.all๋กœ ์—ฌ๋Ÿฌ ์š”์ฒญ์„ ๋™์‹œ์— ์ฒ˜๋ฆฌํ•˜๋ฉด ์„ฑ๋Šฅ์ด ํฌ๊ฒŒ ํ–ฅ์ƒ๋ผ.
6
CORS, ๋ณด์•ˆ, ๋””๋ฐ”์šด์Šค ๊ฐ™์€ ์‹ค์ „ ๊ฐœ๋…๋“ค๋„ ๊ผญ ์ฑ™๊ฒจ์•ผ ํ•ด.
๐ŸŒŸ ๋งˆ์ง€๋ง‰ ํ•œ๋งˆ๋””: AJAX์™€ Fetch API๋Š” ํ˜„๋Œ€ ์›น ๊ฐœ๋ฐœ์˜ ํ•ต์‹ฌ์ด์•ผ. ์žฌ๋Šฅ๋„ท์ฒ˜๋Ÿผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ฐ์ดํ„ฐ๊ฐ€ ์—…๋ฐ์ดํŠธ๋˜๋Š” ํ”Œ๋žซํผ๋“ค๋„ ๋ชจ๋‘ ์ด ๊ธฐ์ˆ ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๋™์ž‘ํ•ด. ์˜ค๋Š˜ ๋ฐฐ์šด ๋‚ด์šฉ์„ ์ง์ ‘ ์ž‘์€ ํ”„๋กœ์ ํŠธ์— ์ ์šฉํ•ด๋ณด๋ฉด์„œ ์†์— ์ตํ˜€๋ด! ์ฝ”๋“œ๋Š” ์ฝ๋Š” ๊ฒƒ๋ณด๋‹ค ์ง์ ‘ ์ณ๋ณด๋Š” ๊ฒŒ 100๋ฐฐ ํšจ๊ณผ์ ์ด์•ผ. ๐Ÿ’ช

ํ˜น์‹œ ๋” ๊นŠ์ด ๊ณต๋ถ€ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด Axios(Fetch๋ฅผ ๋” ํŽธํ•˜๊ฒŒ ์“ธ ์ˆ˜ ์žˆ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ), SWR์ด๋‚˜ React Query(๋ฐ์ดํ„ฐ ํŽ˜์นญ ์ƒํƒœ ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ), WebSocket(์‹ค์‹œ๊ฐ„ ์–‘๋ฐฉํ–ฅ ํ†ต์‹ ) ๊ฐ™์€ ์ฃผ์ œ๋“ค๋„ ์ฐพ์•„๋ด. ๋น„๋™๊ธฐ ํ†ต์‹ ์˜ ์„ธ๊ณ„๋Š” ์ •๋ง ๋„“๊ณ  ๊นŠ๊ฑฐ๋“ ! ๐ŸŒŠ

๊ทธ๋ฆฌ๊ณ  ๋‹ค์–‘ํ•œ ๊ฐœ๋ฐœ ์ง€์‹์„ ๊ณต์œ ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ์žฌ๋Šฅ๋„ท์˜ ์ง€์‹์ธ์˜ ์ˆฒ ๊ฐ™์€ ๊ณต๊ฐ„์„ ํ™œ์šฉํ•ด๋ด. ๋‚˜์ฒ˜๋Ÿผ ์•„๋Š” ๊ฑธ ๊ธ€๋กœ ์ •๋ฆฌํ•˜๋‹ค ๋ณด๋ฉด ์‹ค๋ ฅ์ด ํ›จ์”ฌ ๋นจ๋ฆฌ ๋Š˜๊ฑฐ๋“ ! ๐Ÿ˜„

๋Œ“๊ธ€ ์ž‘์„ฑ

์ด ๊ธ€์— ๋Œ€ํ•œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ƒ๊ฐ์„ ๋“ค๋ ค์ฃผ์„ธ์š”

๋Œ“๊ธ€ 0
๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.

์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค.