콘텐츠로 이동

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는 “미래에 완료될 작업”을 객체로 표현한다. .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/await
async 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~2005XMLHttpRequest비동기 가능하나 콜백 기반, 장황함
2012~2015Promise체이닝으로 가독성 향상, 오류 처리 일관화
2017~fetch() + async/await동기 코드처럼 읽히는 현대 표준

브라우저는 보안상 동일 출처 정책(Same-Origin Policy) 을 적용한다. http://localhost:3000 (프론트엔드)에서 http://localhost:8000 (FastAPI)으로 요청을 보내면 출처가 다르므로 브라우저가 차단한다.

서버가 응답 헤더에 허용을 명시해야 한다.

# FastAPI — CORS 설정
from fastapi import FastAPI
from 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 FastAPI
from 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의 httpxaiohttp 로 외부 API를 호출하는 것과 구조가 동일하다. await fetch(...)await httpx.get(...) 와 개념적으로 같다.

  • 브라우저 메인 스레드는 UI 렌더링과 JavaScript 실행을 함께 담당하므로, 네트워크 요청은 반드시 비동기로 처리해야 화면 멈춤을 방지할 수 있다
  • XMLHttpRequest → Promise → fetch() + async/await 순으로 진화했으며, 현재 표준은 fetch()async/await 조합이다
  • 프론트엔드와 백엔드의 출처(origin)가 다르면 CORS 설정이 필요하다. FastAPI에서는 CORSMiddleware 로 간단히 처리한다
  • Python async/await와 개념이 동일하므로 ML 엔지니어라면 패턴을 빠르게 익힐 수 있다