// 이 파일이 하는 일: "비동기 JavaScript: fetch와 Promise 정복" 코스 — 동기 코드가 화면을
// 얼리는 순간에서 출발해, 콜백 지옥 → Promise → async/await → fetch → 병렬 처리 →
// 실전 패턴(로딩·취소·타임아웃)까지 7개 섹션으로 안내하는 정적 학습 페이지.
// 프레이밍은 "서버와 대화하는 코드" — 기초 문법을 아는 학생이 실제로 돌아가는 웹 서비스를
// 만들 때 반드시 넘어야 하는 첫 고개인 비동기를, 환율 API 실습으로 손에 잡히게 만든다.
// 학습 포인트: API 호출이 없는 '내용 고정' 페이지다. 스타일은 styles/global.css의
// 가이드 전용 클래스(step-card, code-block, tip 등)를 재사용하고 색은 var(--*)만 쓴다(하드코딩 금지).
// 예제 코드는 JSX 중괄호/백틱 충돌을 피하려고 파일 상단의 백틱 문자열 상수로 정의한다.

import { Link } from 'react-router-dom';

// ── 예제 코드 상수들 ──

const CODE_FREEZE = `// 버튼을 누르면 3초 동안 '아무것도' 못 하는 코드 — 콘솔에서 직접 체험해 보세요
function freezeFor3Seconds() {
  const start = Date.now();
  while (Date.now() - start < 3000) {
    // 3초 동안 CPU를 붙잡고 제자리 돌기
  }
  console.log('끝!');
}

// 이 함수가 도는 동안 클릭·스크롤·입력이 전부 멈춥니다.
// JavaScript는 '한 번에 한 가지 일'만 하는 싱글 스레드라서,
// 오래 걸리는 일을 그 자리에서 기다리면 화면 전체가 얼어붙어요.
// 네트워크 요청을 이런 식으로 기다렸다간? 서버가 느린 만큼 앱이 얼겠죠.`;

const CODE_KITCHEN = `주방(JS 엔진)에는 요리사가 '한 명'뿐입니다.

[주문서 더미(큐)]   ← 끝난 일들의 쪽지(클릭 발생, 타이머 완료, 응답 도착)가 쌓이는 곳
       ↓ 한 장씩 집어서
[요리사(콜 스택)]   ← 지금 만들고 있는 요리 딱 하나
       ↑ 기다리는 일은 위임
[홀 직원(브라우저)] ← 타이머, 네트워크 요청처럼 '언제 끝날지 모르는 일'을
                      요리사 대신 맡아 주는 손들

핵심: 요리사는 절대 오븐 앞에서 멍하니 기다리지 않아요.
"다 구워지면 주문서 더미에 쪽지 넣어 줘" 하고 다음 요리로 넘어갑니다.
요리사가 손이 빌 때마다 주문서 더미에서 쪽지를 집어 처리하는
이 순환 구조가 바로 '이벤트 루프'입니다.`;

const CODE_CALLBACK = `// 콜백: "이 일이 끝나면 이 함수를 불러 줘" — 함수를 인자로 넘기는 방식
setTimeout(function () {
  console.log('B: 3초 뒤에 실행됩니다');
}, 3000);

console.log('A: 이 줄이 먼저 찍힙니다!');

// 출력 순서: A → (3초 뒤) → B
// setTimeout은 타이머를 브라우저(홀 직원)에게 맡기고 '즉시' 다음 줄로 넘어가요.
// 우리가 넘긴 함수는 쪽지에 적혀 있다가, 3초 뒤에 이벤트 루프가 집어 실행합니다.`;

const CODE_HELL = `// 환율을 받고 → 환전 계산하고 → 기록을 저장하는 3단계를 콜백으로 쓰면...
getRate('USD', function (rate) {
  convert(1000, rate, function (result) {
    saveHistory(result, function () {
      console.log('완료!');
    }, function (err) {
      console.error('기록 실패', err);   // 에러 처리 ①
    });
  }, function (err) {
    console.error('계산 실패', err);     // 에러 처리 ②
  });
}, function (err) {
  console.error('환율 실패', err);       // 에러 처리 ③
});

// 오른쪽으로 계속 밀려나는 계단 모양 — 이른바 '콜백 지옥(callback hell)'.
// 단계가 하나 늘 때마다 들여쓰기가 한 층 깊어지고,
// 에러 처리는 세 군데로 흩어져서 하나라도 빠뜨리기 딱 좋습니다.`;

const CODE_PROMISE_STATE = `// Promise: "지금은 결과가 없지만, 나중에 꼭 주겠다"는 약속 객체
const promise = fetch('https://open.er-api.com/v6/latest/USD');

// 갓 만들어진 promise의 상태는 pending(대기 중).
// 시간이 지나면 둘 중 하나로 '단 한 번' 정착합니다.
//
//   pending ──┬──→ fulfilled(이행)  성공! 결과값을 품는다
//             └──→ rejected(거부)   실패! 에러를 품는다
//
// 한번 정착(settled)한 상태는 절대 다시 바뀌지 않아요.
// fulfilled였다가 rejected가 되는 일은 없습니다 — 그래서 '믿고 기다릴' 수 있어요.`;

const CODE_THEN = `// 콜백 지옥의 3단계를 then 체이닝으로 다시 쓰면
getRate('USD')
  .then(function (rate) { return convert(1000, rate); })    // 1단계 → 2단계
  .then(function (result) { return saveHistory(result); })  // 2단계 → 3단계
  .then(function () { console.log('완료!'); })
  .catch(function (err) {
    // 위 어느 단계에서 실패해도 전부 여기 '한 곳'으로 모입니다
    console.error('어딘가에서 실패:', err);
  });

// 같은 일을 하는데 — 계단이 사라지고, 에러 처리가 catch 하나로 모였어요.
// 비밀은 then이 '항상 새 Promise를 돌려준다'는 것.
// 그래서 사슬(chain)처럼 계속 이어 붙일 수 있습니다.`;

const CODE_AWAIT = `// 같은 로직을 async/await로 — '동기처럼 읽히는' 비동기 코드
async function run() {
  try {
    const rate = await getRate('USD');        // Promise가 정착할 때까지 대기
    const result = await convert(1000, rate);
    await saveHistory(result);
    console.log('완료!');
  } catch (err) {
    // then 체인의 catch와 같은 역할 — 실패가 한 곳으로 모입니다
    console.error('어딘가에서 실패:', err);
  }
}

// await는 마법이 아니라 '문법 설탕'이에요. 속은 여전히 Promise인데,
// then 체인을 위→아래로 읽히는 모양으로 바꿔 줄 뿐입니다.
// 규칙 두 가지: ① await는 async 함수 안에서만 ② await가 기다리는 건 Promise`;

const CODE_FETCH = `// fetch는 '2단계'로 동작합니다 — 응답 도착 확인 → 본문 해석
async function getUsdRate() {
  const res = await fetch('https://open.er-api.com/v6/latest/USD'); // ① 응답 도착

  if (!res.ok) {                        // ② 상태 코드 확인은 '습관'!
    throw new Error('HTTP 오류: ' + res.status);
  }

  const data = await res.json();        // ③ 본문(JSON)을 마저 받아 해석 — 이것도 Promise
  return data.rates.KRW;                // 1달러 = ? 원
}

// 함정: fetch는 404·500 응답에도 reject되지 '않아요'!
// 서버와 연결 자체가 실패(네트워크 끊김 등)했을 때만 reject됩니다.
// 서버가 "그런 거 없는데요(404)"라고 '대답'한 건 fetch 입장에선 성공이거든요.
// 그래서 res.ok(상태 200~299) 확인은 선택이 아니라 습관이어야 합니다.`;

const CODE_POST = `// 옵션 객체로 메서드·헤더·본문을 지정하면 POST도 보낼 수 있어요
const res = await fetch('/api/favorites', {
  method: 'POST',                              // 기본값은 GET
  headers: {
    'Content-Type': 'application/json',        // "본문은 JSON이야"라고 알려주기
  },
  body: JSON.stringify({ currency: 'USD', amount: 1000 }),  // 객체 → JSON 문자열
});

if (!res.ok) throw new Error('저장 실패: ' + res.status);
const saved = await res.json();

// 보낼 때는 JSON.stringify(객체 → 문자열), 받을 때는 res.json()(문자열 → 객체).
// 이 왕복 변환이 '서버와 대화'의 기본 문법입니다.`;

const CODE_PARALLEL = `// 방법 A: 순차 await — 앞 요청이 '끝나야' 다음이 출발 (직렬)
const usd = await fetch(BASE + '/USD').then(function (r) { return r.json(); });
const eur = await fetch(BASE + '/EUR').then(function (r) { return r.json(); });
const jpy = await fetch(BASE + '/JPY').then(function (r) { return r.json(); });
// 요청당 0.3초라면 → 합계 약 0.9초

// 방법 B: Promise.all — 셋을 '동시에' 출발시키고 전부 끝나길 기다림 (병렬)
const [usd2, eur2, jpy2] = await Promise.all([
  fetch(BASE + '/USD').then(function (r) { return r.json(); }),
  fetch(BASE + '/EUR').then(function (r) { return r.json(); }),
  fetch(BASE + '/JPY').then(function (r) { return r.json(); }),
]);
// 동시에 달리므로 → 약 0.3초. '가장 느린 요청 하나'만큼만 걸려요.

// 서로 의존하지 않는 요청(앞 결과가 뒤 요청에 필요 없는 경우)이라면
// 병렬이 기본값이어야 합니다. console.time()으로 직접 재 보세요!`;

const CODE_ALLSETTLED = `// Promise.all의 성격: 하나라도 실패하면 '전체가' 실패(reject)
// USD·EUR은 성공했는데 JPY만 실패해도 → 성공한 둘의 결과까지 함께 버려집니다.
// "전부 있어야만 의미 있는 데이터"라면 이게 오히려 올바른 동작이에요.

// 부분 실패를 허용하고 싶다면 → Promise.allSettled
const results = await Promise.allSettled([pUsd, pEur, pJpy]);

results.forEach(function (r) {
  if (r.status === 'fulfilled') {
    console.log('성공:', r.value);      // 성공한 것만 화면에 그리고
  } else {
    console.log('실패:', r.reason);     // 실패한 통화엔 "조회 실패" 표시
  }
});

// allSettled는 절대 reject되지 않아요 — 성공이든 실패든
// '전원의 결과 보고서'를 배열로 돌려줍니다.`;

const CODE_WIDGET = `// 실습 골격: 로딩·성공·실패 3상태를 갖춘 환율 검색 위젯 (바닐라 JS)
const box = document.querySelector('#rate-box');

async function loadRate(code) {
  box.innerHTML = '<span class="spin"></span> 불러오는 중...';   // 상태 ① 로딩
  try {
    const res = await fetch('https://open.er-api.com/v6/latest/' + code);
    if (!res.ok) throw new Error('HTTP ' + res.status);
    const data = await res.json();
    const krw = Math.round(data.rates.KRW);
    box.textContent = '1 ' + code + ' = ' + krw + '원';          // 상태 ② 성공
  } catch (err) {
    box.innerHTML = '환율을 불러오지 못했어요. ' +
      '<button id="retry">다시 시도</button>';                    // 상태 ③ 실패
    document.querySelector('#retry')
      .addEventListener('click', function () { loadRate(code); }); // 재시도 = 같은 함수 재호출
  }
}

loadRate('USD');

// 포인트: 화면은 언제나 로딩·성공·실패 '셋 중 하나'의 상태에 있습니다.
// 세 상태를 전부 코드로 표현했는지가 '되다 만 위젯'과 '완성된 위젯'의 차이예요.`;

const CODE_ABORT = `// AbortController — 이미 보낸 요청에 '그만!'을 외치는 리모컨
const controller = new AbortController();

const promise = fetch(url, { signal: controller.signal });  // 리모컨 신호선 연결

// 사용자가 검색 통화를 USD → EUR로 바꿨다면? 이전 요청은 취소
controller.abort();   // promise가 'AbortError'라는 이름의 에러로 reject됨

// ── 응용: 타임아웃 패턴 — 5초 안에 응답 없으면 포기 ──
const ctrl = new AbortController();
const timer = setTimeout(function () { ctrl.abort(); }, 5000);

try {
  const res = await fetch(url, { signal: ctrl.signal });
  clearTimeout(timer);                 // 제때 왔으면 타이머 해제
  const data = await res.json();
} catch (err) {
  if (err.name === 'AbortError') {
    // 느린 서버 — 무한 로딩 대신 "다시 시도" 버튼을 보여 줍시다
  } else {
    // 그 밖의 진짜 실패 (네트워크 끊김 등)
  }
}`;

// ── 이 페이지 안에서만 쓰는 작은 부품들 ──
// 학습 포인트: 같은 모양이 반복되면 컴포넌트로 뽑는다(React 입문 섹션 2). props만 바꿔 재사용.
function Section({ n, title, sub, children }) {
  return (
    <section className="step-card" id={`sec-${n}`}>
      <div className="step-head">
        <span className="step-num">{n}</span>
        <div>
          <h3>{title}</h3>
          {sub && <span className="step-sub">{sub}</span>}
        </div>
      </div>
      <div className="step-body">{children}</div>
    </section>
  );
}

function Code({ children }) {
  return <pre className="code-block"><code>{children}</code></pre>;
}

const SECTIONS = [
  { n: 1, label: '왜 비동기인가' },
  { n: 2, label: '콜백 지옥' },
  { n: 3, label: 'Promise 3상태' },
  { n: 4, label: 'async/await' },
  { n: 5, label: 'fetch 실전' },
  { n: 6, label: 'Promise.all' },
  { n: 7, label: '로딩·취소·타임아웃' },
];

export default function Lv2AsyncJavascriptPage() {
  return (
    <div>
      {/* 히어로: 이 코스가 어디서 출발해 어디까지 가는지 */}
      <div className="hero">
        <div className="eyebrow">Course · 중급 과정</div>
        <h1>비동기 JavaScript<br />— fetch와 Promise 정복</h1>
        <p>
          기초 문법을 배웠는데도 "진짜 서비스"가 안 만들어지는 이유의 절반은{' '}
          <strong>비동기</strong>에 있어요. 서버는 언제 대답할지 모르는데, JavaScript는 한 번에
          한 가지 일밖에 못 하니까요. 이 코스에서 콜백 지옥에서 <strong>async/await</strong>까지의
          진화를 따라 걷고, 마지막엔 로딩 스피너·에러 메시지·재시도 버튼을 갖춘{' '}
          <strong>환율 검색 위젯</strong>을 직접 만들어 봅니다.
        </p>
        <div className="chip-row">
          <span className="chip">예상 소요 60분</span>
          <span className="chip">실습: 환율 검색 위젯</span>
          <span className="chip">선수 지식: JS 기초 문법</span>
        </div>
      </div>

      {/* 섹션 바로가기 */}
      <nav className="pill-nav">
        {SECTIONS.map((s) => (
          <a key={s.n} href={`#sec-${s.n}`}>
            <b>{s.n}</b>
            {s.label}
          </a>
        ))}
      </nav>

      <Section n={1} title="왜 비동기인가" sub="동기 코드가 화면을 멈추게 하는 순간">
        <p>
          출발점은 체험이에요. 아래 코드를 브라우저 콘솔에 붙여 넣고 실행해 보세요. 함수가 도는
          3초 동안 <strong>클릭도, 스크롤도, 입력도 전부 먹통</strong>이 됩니다. JavaScript는{' '}
          <strong>싱글 스레드</strong> — 일꾼이 한 명뿐이라, 그 일꾼이 제자리에서 기다리면 화면을
          그릴 사람이 없거든요.
        </p>
        <Code>{CODE_FREEZE}</Code>
        <p>
          그런데 웹 서비스의 핵심 일과는 <strong>네트워크 요청</strong>이에요. 서버가 0.1초 만에
          대답할지, 3초 걸릴지, 영영 대답이 없을지 <strong>보내기 전엔 알 수 없죠.</strong> 이
          "언제 끝날지 모르는 일"을 일꾼이 그 자리에서 기다리면 앱은 서버가 느린 만큼 얼어붙어요.
          그래서 JavaScript는 다른 전략을 택합니다 — <strong>기다리지 않고, 맡기는</strong> 거예요.
        </p>
        <Code>{CODE_KITCHEN}</Code>
        <div className="tip">
          <b>이 비유를 기억하세요</b> 요리사(콜 스택)는 한 명, 홀 직원(브라우저)이 기다리는 일을
          대신 맡고, 끝난 일은 주문서 더미(큐)에 쌓였다가 요리사 손이 빌 때 처리된다 — 이{' '}
          <strong>이벤트 루프</strong> 그림 하나면 이 코스의 모든 문법(콜백·Promise·await)이
          "주문서를 어떻게 예쁘게 쓰느냐"의 문제로 보이기 시작합니다.
        </div>
      </Section>

      <Section n={2} title="콜백과 콜백 지옥" sub="'나중에 실행해 줘'의 가장 원시적인 표현">
        <p>
          "일을 맡겼으면, 끝났을 때 뭘 할지도 알려 줘야" 하죠. 가장 원시적인 방법이{' '}
          <strong>콜백(callback)</strong> — 나중에 불러 달라고 <strong>함수를 인자로 넘기는</strong>{' '}
          방식입니다. 이미 써 보셨을 거예요. <span className="icode">addEventListener</span>의 두
          번째 인자, <span className="icode">setTimeout</span>의 첫 번째 인자가 전부 콜백입니다.
        </p>
        <Code>{CODE_CALLBACK}</Code>
        <p>
          한 단계짜리 일이면 콜백도 충분해요. 문제는 <strong>순차 작업</strong>입니다. "환율을
          받아서 → 그걸로 환전 계산을 하고 → 결과를 기록한다"처럼 앞 결과가 다음 단계에 필요하면,
          콜백 안에 콜백을, 그 안에 또 콜백을 넣을 수밖에 없어요.
        </p>
        <Code>{CODE_HELL}</Code>
        <div className="warn">
          <b>진짜 문제는 들여쓰기가 아니에요</b> 계단 모양은 보기 싫은 정도지만,{' '}
          <strong>에러 처리가 세 군데로 흩어진 것</strong>은 사고로 이어집니다. 단계마다 실패
          콜백을 따로 달아야 하고, 하나라도 깜빡하면 그 에러는 조용히 증발해요. "실패를 한 곳에
          모을 수 없을까?" — 이 질문에 대한 답이 다음 섹션의 Promise입니다.
        </div>
      </Section>

      <Section n={3} title="Promise의 3가지 상태" sub="pending → fulfilled 또는 rejected, 단 한 번">
        <p>
          <strong>Promise</strong>는 "지금은 결과가 없지만 나중에 꼭 주겠다"는{' '}
          <strong>약속을 담은 객체</strong>예요. 콜백처럼 함수를 미리 찔러 넣는 게 아니라, 일단
          약속 객체를 <strong>손에 받아 두고</strong> 거기에 "성공하면 이거, 실패하면 저거"를
          붙이는 방식이죠. 약속의 일생은 세 상태로 요약됩니다.
        </p>
        <Code>{CODE_PROMISE_STATE}</Code>
        <svg className="diagram" viewBox="0 0 640 220" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Promise 상태 전이 다이어그램. pending 상태에서 시작해 성공하면 fulfilled, 실패하면 rejected로 단 한 번 전이하며, 한번 정착한 상태는 다시 바뀌지 않는다.">
          <rect x="40" y="80" width="150" height="60" rx="10" fill="var(--card)" stroke="var(--amber)" strokeWidth="2" />
          <text x="115" y="106" textAnchor="middle" fontSize="14" fontWeight="600" fill="var(--ink)">pending</text>
          <text x="115" y="126" textAnchor="middle" fontSize="11" fill="var(--muted)">대기 중...</text>

          <rect x="430" y="24" width="170" height="60" rx="10" fill="var(--card)" stroke="var(--teal)" strokeWidth="2" />
          <text x="515" y="50" textAnchor="middle" fontSize="14" fontWeight="600" fill="var(--ink)">fulfilled</text>
          <text x="515" y="70" textAnchor="middle" fontSize="11" fill="var(--muted)">성공 · 결과값 보유 → then</text>

          <rect x="430" y="136" width="170" height="60" rx="10" fill="var(--card)" stroke="var(--primary)" strokeWidth="2" />
          <text x="515" y="162" textAnchor="middle" fontSize="14" fontWeight="600" fill="var(--ink)">rejected</text>
          <text x="515" y="182" textAnchor="middle" fontSize="11" fill="var(--muted)">실패 · 에러 보유 → catch</text>

          {/* pending → fulfilled */}
          <line x1="190" y1="96" x2="422" y2="60" stroke="var(--teal)" strokeWidth="2" />
          <path d="M430 58 L418 56 L420 66 Z" fill="var(--teal)" />
          <text x="300" y="60" textAnchor="middle" fontSize="11" fill="var(--teal)">성공 (resolve)</text>

          {/* pending → rejected */}
          <line x1="190" y1="124" x2="422" y2="160" stroke="var(--primary)" strokeWidth="2" />
          <path d="M430 162 L420 154 L418 164 Z" fill="var(--primary)" />
          <text x="300" y="164" textAnchor="middle" fontSize="11" fill="var(--primary)">실패 (reject)</text>

          <text x="320" y="212" textAnchor="middle" fontSize="12" fill="var(--muted)">전이는 단 한 번 — 정착(settled)한 뒤엔 절대 되돌아가지 않는다</text>
        </svg>
        <p className="muted" style={{ fontSize: 12.5, textAlign: 'center', marginTop: 2 }}>그림: Promise는 pending에서 출발해 fulfilled 또는 rejected 중 하나로 단 한 번 정착한다</p>
        <p>
          약속에 후속 작업을 붙이는 문법이 <span className="icode">then</span>(성공 시)과{' '}
          <span className="icode">catch</span>(실패 시)예요. 그리고{' '}
          <span className="icode">then</span>은 <strong>항상 새 Promise를 반환</strong>하기 때문에,
          콜백 지옥의 계단을 <strong>수평 사슬</strong>로 펼 수 있습니다.
        </p>
        <Code>{CODE_THEN}</Code>
        <div className="tip">
          <b>비유</b> Promise는 <strong>카페 진동벨</strong>이에요. 주문하면(요청) 음료 대신
          진동벨(Promise)을 먼저 받죠. 벨을 손에 쥔 채 자리에서 딴 일을 하다가 — 울리면(fulfilled)
          음료를 받고, 점원이 와서 "재료가 떨어졌어요" 하면(rejected) 환불 처리를 합니다. 벨이
          두 번 다르게 울리는 일은 없어요. <strong>단 한 번, 둘 중 하나.</strong>
        </div>
      </Section>

      <Section n={4} title="async/await로 다시 쓰기" sub="then 체인을 '위에서 아래로' 읽히게">
        <p>
          then 체인은 훌륭하지만, 로직이 길어지면{' '}
          <span className="icode">function</span>과 <span className="icode">return</span>이
          반복되며 눈이 미끄러져요. 그래서 나온 최종 진화형이{' '}
          <strong>async/await</strong> — 같은 Promise를 <strong>동기 코드처럼 읽히는 모양</strong>
          으로 쓰는 문법입니다. 섹션 3의 체인을 그대로 옮겨 볼게요.
        </p>
        <Code>{CODE_AWAIT}</Code>
        <p>
          짚어 둘 것은 <strong>await가 '문법 설탕(syntactic sugar)'</strong>이라는 점이에요. 새
          동작이 생긴 게 아니라, then 체인과 <strong>같은 일</strong>을 다르게 표기할 뿐입니다.{' '}
          <span className="icode">await</span> 뒤의 Promise가 정착할 때까지 그 함수의 실행이
          멈추는데 — 섹션 1의 <span className="icode">while</span> 루프와 달리{' '}
          <strong>화면은 얼지 않아요.</strong> 함수만 잠들고, 일꾼(콜 스택)은 풀려나 다른 주문서를
          처리하러 가거든요.
        </p>
        <p>
          에러 처리는 우리가 아는 <span className="icode">try/catch</span> 그대로입니다.{' '}
          <span className="icode">try</span> 블록 안 <strong>어느 await에서 실패해도</strong> 실행이{' '}
          <span className="icode">catch</span>로 점프해요. 콜백 지옥에서 세 군데로 흩어졌던 에러
          처리가, 여기선 익숙한 문법 한 곳으로 모입니다.
        </p>
        <div className="warn">
          <b>자주 하는 실수</b> <span className="icode">await</span>를{' '}
          <span className="icode">async</span> 없는 보통 함수 안에 쓰면 문법 에러가 납니다. 그리고{' '}
          <span className="icode">async</span> 함수의 반환값은 <strong>항상 Promise</strong>예요 —{' '}
          <span className="icode">return 42</span>를 해도 밖에서는{' '}
          <span className="icode">Promise</span>로 감싸져 나오니, 호출하는 쪽에서도{' '}
          <span className="icode">await</span>가 필요합니다. "async는 전염된다"고 기억해 두세요.
        </div>
      </Section>

      <Section n={5} title="fetch로 진짜 API 호출" sub="response.ok 확인은 선택이 아니라 습관">
        <p>
          이제 도구가 갖춰졌으니 <strong>진짜 서버</strong>와 대화해 봅시다.{' '}
          <span className="icode">fetch</span>는 브라우저에 내장된 HTTP 요청 함수로, Promise를
          돌려줘요. 구조에서 헷갈리기 쉬운 건 <strong>await가 두 번</strong> 나온다는 점입니다 —
          ① 응답(헤더)이 도착하길 기다리고, ② 본문이 다 내려와 해석되길 또 기다립니다.
        </p>
        <Code>{CODE_FETCH}</Code>
        <p>
          주석의 함정을 꼭 기억하세요. <span className="icode">fetch</span>는{' '}
          <strong>404나 500 같은 에러 응답에도 reject되지 않습니다.</strong> 서버가 "실패"라고
          대답한 것과, 대답 자체가 안 온 것(네트워크 단절)을 구분하기 때문이에요. HTTP 수준의
          실패를 잡으려면 <span className="icode">res.ok</span>(상태 코드 200~299면 true)를 확인해
          직접 <span className="icode">throw</span>해야, 섹션 4에서 만든{' '}
          <span className="icode">catch</span> 그물에 걸립니다.
        </p>
        <p>
          데이터를 <strong>보내는</strong> 쪽도 옵션 객체 하나로 해결돼요. 메서드, 헤더, 본문을
          지정하면 POST 요청이 됩니다.
        </p>
        <Code>{CODE_POST}</Code>
        <div className="tip">
          <b>직접 확인해 보기</b> 브라우저 콘솔에서{' '}
          <span className="icode">await fetch('https://open.er-api.com/v6/latest/USD').then(r =&gt; r.json())</span>
          를 실행해 보세요. 공개 환율 API라 키 없이도 응답이 옵니다.{' '}
          <span className="icode">rates.KRW</span>가 오늘의 원/달러 환율이에요 — 섹션 7 실습에서 이
          API를 그대로 씁니다. <span className="kbd">F12</span> → Network 탭에서 방금 나간 요청의
          상태 코드와 응답 본문도 눈으로 확인해 보세요.
        </div>
      </Section>

      <Section n={6} title="병렬 처리: Promise.all" sub="순차 await의 숨은 낭비를 걷어내기">
        <p>
          await가 편하다 보니 생기는 <strong>흔한 낭비</strong>가 있어요. 서로 상관없는 요청
          세 개를 순서대로 <span className="icode">await</span>하면, 앞 요청이 끝나야 다음이{' '}
          <strong>출발</strong>합니다. 달러·유로·엔 환율은 서로를 필요로 하지 않는데도요.
        </p>
        <Code>{CODE_PARALLEL}</Code>
        <p>
          <span className="icode">Promise.all</span>은 Promise 배열을 받아{' '}
          <strong>전부 fulfilled되면</strong> 결과 배열을 (넣은 순서 그대로) 돌려줍니다. 단,
          성격이 확실해요 — <strong>하나라도 rejected되면 전체가 즉시 reject</strong>됩니다.
          "전원 성공 아니면 전체 실패"죠. 부분 실패를 살리고 싶을 땐{' '}
          <span className="icode">Promise.allSettled</span>를 씁니다.
        </p>
        <Code>{CODE_ALLSETTLED}</Code>
        <div className="tip">
          <b>고르는 기준</b> 결과들이 <strong>한 몸</strong>이라 하나라도 빠지면 의미가 없다 →{' '}
          <span className="icode">all</span>(빨리 실패하고 전체 재시도). 결과들이{' '}
          <strong>각자 독립</strong>이라 되는 것만이라도 보여 주고 싶다 →{' '}
          <span className="icode">allSettled</span>(통화별로 성공/실패 표시). 문법이 아니라{' '}
          <strong>화면 요구사항</strong>이 답을 정합니다.
        </div>
      </Section>

      <Section n={7} title="실전 패턴: 로딩·취소·타임아웃" sub="환율 위젯을 '완성품'으로 만드는 마지막 조각">
        <p>
          문법을 다 배워도 실전 코드엔 한 층이 더 있어요. 요청이 나가 있는 동안 사용자는{' '}
          <strong>뭔가를 보고 있어야</strong> 하고, 실패하면 <strong>다음 행동</strong>(재시도)을
          제안받아야 하죠. 그래서 서버와 대화하는 화면은 언제나{' '}
          <strong>로딩 · 성공 · 실패</strong>라는 3상태 중 하나에 있습니다. 이번 코스의 실습
          — 환율 검색 위젯의 골격이 바로 이 3상태 관리예요.
        </p>
        <Code>{CODE_WIDGET}</Code>
        <p>
          여기에 실무 감각 두 가지를 더하면 완성입니다. 첫째, <strong>취소</strong>. 사용자가
          검색을 연타하면 이전 요청은 의미가 없어지는데, 늦게 도착한 옛 응답이 새 화면을 덮어쓰는
          사고가 나요. 둘째, <strong>타임아웃</strong>. 서버가 영영 대답하지 않으면 로딩 스피너가
          무한히 돕니다. 둘 다 <span className="icode">AbortController</span> 하나로 해결돼요.
        </p>
        <Code>{CODE_ABORT}</Code>
        <div className="warn">
          <b>AbortError는 '실패'가 아닐 수 있어요</b> 사용자가 검색어를 바꿔서{' '}
          <strong>우리가 스스로</strong> 취소한 요청도 catch로 들어옵니다. 그걸 "에러 발생!"으로
          표시하면 이상하겠죠. 그래서 catch 안에서{' '}
          <span className="icode">err.name === 'AbortError'</span>를 구분하는 습관이 필요합니다 —
          의도한 취소는 조용히, 진짜 실패만 에러 메시지로.
        </div>
        <div className="tip">
          <b>실습 과제</b> 위 두 코드를 합쳐 보세요. 통화 코드 입력창 + 검색 버튼을 만들고 →
          검색 시 로딩 스피너 → 성공하면 환율 표시 → 실패하면 에러 메시지와 재시도 버튼 →
          5초 타임아웃까지. 완성하면 여러분은 <strong>실제 서비스의 데이터 화면</strong>과 똑같은
          구조를 처음부터 끝까지 만들어 본 겁니다.
        </div>
      </Section>

      <div className="step-card">
        <div className="step-body">
          <h3 style={{ marginBottom: 6 }}>⏳ 여기까지 왔다면</h3>
          <p className="muted">
            이제 "서버와 대화하는 코드"의 전체 지형이 손에 있어요. 이벤트 루프(주방), 콜백 지옥이
            남긴 교훈, Promise의 3상태(진동벨), async/await라는 문법 설탕, fetch의 2단계와{' '}
            <strong>res.ok 습관</strong>, Promise.all의 병렬 감각, 그리고 로딩·취소·타임아웃까지 —
            환율 위젯 하나에 전부 담아 봤습니다. 그런데 서버에서 받아 온 데이터는 대부분{' '}
            <strong>배열</strong>이에요. 받아 온 환율 목록을 골라내고(filter), 바꾸고(map),
            합치는(reduce) 손맛이 다음 관문입니다. 다음{' '}
            <Link to="/learn/lv2-array-data-wrangling"><strong>배열 메서드로 데이터 요리하기</strong></Link>{' '}
            코스에서, fetch로 받아 온 데이터를 화면에 올리기 좋게 다듬는 기술을 이어서 익혀 봅시다.
          </p>
        </div>
      </div>
    </div>
  );
}
