AJAX, Fetch API, 그리고 비동기 통신
왜 비동기가 필요한가
섹션 제목: “왜 비동기가 필요한가”브라우저는 단일 메인 스레드(UI 스레드)에서 JavaScript 실행, DOM 업데이트, 사용자 입력 처리를 모두 담당한다. 이 스레드가 블로킹되면 화면이 멈춘다.
// 동기 방식 — 이렇게 하면 안 된다const response = httpGetSync("https://api.example.com/data") // 2초 동안 스레드 블로킹// 이 2초 동안 버튼 클릭, 스크롤, 애니메이션 모두 멈춤const data = JSON.parse(response)renderData(data)ML 추론처럼 처리 시간이 긴 작업을 동기로 호출하면 브라우저가 “응답 없음” 상태가 된다. 비동기 I/O는 선택이 아니라 필수다.
XMLHttpRequest: 비동기의 시작 (1999~2015)
섹션 제목: “XMLHttpRequest: 비동기의 시작 (1999~2015)”AJAX(Asynchronous JavaScript And XML)라는 이름을 만든 주인공이다. 2004년 Gmail이 이것을 활용해 페이지 새로고침 없이 이메일을 로드했다.
// XMLHttpRequest — 2005년 스타일const xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () { if (xhr.readyState === 4 && xhr.status === 200) { const data = JSON.parse(xhr.responseText) renderData(data) }}
xhr.onerror = function () { console.error("요청 실패")}
xhr.open("GET", "https://api.example.com/data")xhr.send()동작하지만 문제가 있다. 콜백이 중첩될수록 “콜백 지옥(callback hell)“이 만들어진다.
// 콜백 지옥 — 읽기 어렵고 오류 처리가 복잡xhr1.onreadystatechange = function () { if (xhr1.readyState === 4) { xhr2.onreadystatechange = function () { if (xhr2.readyState === 4) { xhr3.onreadystatechange = function () { // ... } } } }}Promise: 콜백 지옥 탈출 (ES2015)
섹션 제목: “Promise: 콜백 지옥 탈출 (ES2015)”Promise는 “미래에 완료될 작업”을 객체로 표현한다. .then() 체이닝으로 순차 비동기 처리를 가독성 있게 작성할 수 있다.
fetch("https://api.example.com/data") .then((response) => response.json()) .then((data) => renderData(data)) .catch((error) => console.error("오류:", error))Python의 asyncio 와 개념적으로 유사하다. “나중에 결과가 오면 이걸 실행해줘”라는 약속이다.
fetch() + async/await: 현대의 표준 (ES2017~)
섹션 제목: “fetch() + async/await: 현대의 표준 (ES2017~)”async/await 는 Promise 기반 코드를 동기 코드처럼 읽히게 만드는 문법 슈가다. Python의 async/await 와 거의 동일한 개념이다.
// fetch + async/awaitasync function loadData() { try { const response = await fetch("https://api.example.com/data")
if (!response.ok) { throw new Error(`HTTP 오류: ${response.status}`) }
const data = await response.json() renderData(data) } catch (error) { console.error("데이터 로드 실패:", error) }}await 는 메인 스레드를 블로킹하지 않는다. 해당 Promise가 완료될 때까지 현재 함수의 실행을 일시 중단하고 다른 작업을 처리하다가, 완료 시 이어서 실행한다.
진화 흐름 요약
섹션 제목: “진화 흐름 요약”| 시대 | 방식 | 특징 |
|---|---|---|
| 1999~2005 | XMLHttpRequest | 비동기 가능하나 콜백 기반, 장황함 |
| 2012~2015 | Promise | 체이닝으로 가독성 향상, 오류 처리 일관화 |
| 2017~ | fetch() + async/await | 동기 코드처럼 읽히는 현대 표준 |
CORS: 출처 간 요청의 제약
섹션 제목: “CORS: 출처 간 요청의 제약”브라우저는 보안상 동일 출처 정책(Same-Origin Policy) 을 적용한다. http://localhost:3000 (프론트엔드)에서 http://localhost:8000 (FastAPI)으로 요청을 보내면 출처가 다르므로 브라우저가 차단한다.
서버가 응답 헤더에 허용을 명시해야 한다.
# FastAPI — CORS 설정from fastapi import FastAPIfrom fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
app.add_middleware( CORSMiddleware, allow_origins=["http://localhost:3000"], # 프론트엔드 출처 allow_methods=["GET", "POST"], allow_headers=["Content-Type"],)실전: FastAPI /predict 엔드포인트 호출
섹션 제목: “실전: FastAPI /predict 엔드포인트 호출”AI 엔지니어에게 가장 친숙한 패턴 — 프론트엔드에서 모델 추론 API를 호출하는 코드다.
# FastAPI 서버 (backend/main.py)from fastapi import FastAPIfrom pydantic import BaseModel
app = FastAPI()
class PredictRequest(BaseModel): text: str
class PredictResponse(BaseModel): label: str confidence: float
@app.post("/predict", response_model=PredictResponse)async def predict(req: PredictRequest): # 실제로는 모델 추론 return PredictResponse(label="positive", confidence=0.92)// 프론트엔드 (TypeScript)interface PredictRequest { text: string}
interface PredictResponse { label: string confidence: number}
async function callPredict(text: string): Promise<PredictResponse> { const response = await fetch("http://localhost:8000/predict", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ text }), })
if (!response.ok) { throw new Error(`예측 요청 실패: ${response.status}`) }
return response.json() as Promise<PredictResponse>}
// 사용async function handleSubmit(inputText: string) { try { const result = await callPredict(inputText) console.log(`레이블: ${result.label}, 신뢰도: ${result.confidence}`) } catch (error) { console.error("추론 오류:", error) }}Python의 httpx 나 aiohttp 로 외부 API를 호출하는 것과 구조가 동일하다. await fetch(...) 는 await httpx.get(...) 와 개념적으로 같다.
핵심 정리
섹션 제목: “핵심 정리”- 브라우저 메인 스레드는 UI 렌더링과 JavaScript 실행을 함께 담당하므로, 네트워크 요청은 반드시 비동기로 처리해야 화면 멈춤을 방지할 수 있다
- XMLHttpRequest → Promise → fetch() + async/await 순으로 진화했으며, 현재 표준은
fetch()와async/await조합이다 - 프론트엔드와 백엔드의 출처(origin)가 다르면 CORS 설정이 필요하다. FastAPI에서는
CORSMiddleware로 간단히 처리한다 - Python async/await와 개념이 동일하므로 ML 엔지니어라면 패턴을 빠르게 익힐 수 있다