Ajax 비동기 통신 설계: XHR·Fetch부터 XSLT 변환까지

페이지 전체를 새로고침하지 않고 화면 일부만 갱신하는 Ajax의 통신 구조를, XHR·Fetch·XSLT 코드와 함께 실무 관점에서 정리했다.

2026-08-12 · 최초 발행 2025-11-26

검색창에 글자를 몇 개 치면 자동완성 목록이 뜬다. 페이지는 새로고침되지 않는다. 이 익숙한 동작의 이름이 Ajax(Asynchronous JavaScript and XML)다. 브라우저의 JavaScript가 XMLHttpRequest(XHR)나 Fetch API로 서버와 통신하고, 그 응답을 DOM에 반영하는 비동기 처리 흐름이 핵심이다. 초기에는 XML/XSLT 중심으로 데이터를 표현했지만, 지금은 JSON 기반의 경량 통신이 주류다.

통신은 이런 순서로 일어난다

UI 이벤트가 발생하면 JavaScript가 호출되고, Ajax 엔진(XHR 또는 Fetch)이 요청을 만들어 서버로 보낸다. 페이지 전환 없이 상태를 유지한 채 인터랙션이 이어진다는 게 핵심이다. 데이터 포맷은 XML(+XSLT 변환), JSON(경량 객체 직렬화), 경우에 따라 HTML Fragment까지 다양하지만 실무에서는 JSON을 우선한다.

서버Ajax 엔진(XHR/Fetch)JavaScript사용자 UI서버Ajax 엔진(XHR/Fetch)JavaScript사용자 UIalt[성공(2xx)][오류(4xx/5xx/네트워크/타임아웃)]이벤트(입력/스크롤/클릭)요청 생성(메서드, URL, 헤더, Body)HTTP 요청 전송(비동기)응답(JSON/XML/HTML)Promise resolve / onload파싱 및 검증(JSON.parse/XPath/XSLT)DOM 부분 업데이트Promise reject / onerror재시도/백오프/에러 메시지 처리

방식별로 구현해보면

가장 오래된 방식은 XMLHttpRequest다. 호환성이 넓고, onload/ontimeout/onerror 콜백으로 상태를 나눠 처리한다.

<script>
  function search(q) {
    const xhr = new XMLHttpRequest();
    xhr.open("GET", "/api/search?q=" + encodeURIComponent(q), true);
    xhr.responseType = "json";
    xhr.timeout = 5000;
    xhr.onload = () => {
      if (xhr.status === 200) render(xhr.response);
      else handleError("HTTP " + xhr.status);
    };
    xhr.ontimeout = () => handleError("timeout");
    xhr.onerror = () => handleError("network");
    xhr.send();
  }
</script>

현대 브라우저(ES2017+)에서는 Fetch API가 기본이다. AbortController로 타임아웃과 취소를 함께 처리할 수 있다는 게 XHR과의 실무상 차이다.

<script>
  async function loadData() {
    const ac = new AbortController();
    const t = setTimeout(() => ac.abort(), 5000);
    try {
      const res = await fetch("/api/data", {
        headers: { Accept: "application/json" },
        credentials: "same-origin",
        signal: ac.signal,
      });
      if (!res.ok) throw new Error("HTTP " + res.status);
      const data = await res.json();
      render(data);
    } catch (e) {
      handleError(e.message || "fetch error");
    } finally {
      clearTimeout(t);
    }
  }
</script>

XML 데이터를 XSLT로 변환해 렌더링해야 하는 경우도 여전히 남아 있다. XML과 XSLT를 각각 파싱한 뒤 XSLTProcessor로 변환 조각을 만들어 DOM에 붙이는 방식이다.

<script>
  async function renderWithXslt() {
    const [xmlText, xsltText] = await Promise.all([
      fetch("/data.xml").then((r) => r.text()),
      fetch("/view.xslt").then((r) => r.text()),
    ]);
    const parser = new DOMParser();
    const xml = parser.parseFromString(xmlText, "application/xml");
    const xslt = parser.parseFromString(xsltText, "application/xml");
    const proc = new XSLTProcessor();
    proc.importStylesheet(xslt);
    const frag = proc.transformToFragment(xml, document);
    document.getElementById("root").replaceChildren(frag);
  }
</script>

구성 요소를 나눠보면

비동기 페이지 처리는 전체 페이지 리로드 없이 부분 데이터만 갱신하는 것이 핵심이며, UI 상호작용과 네트워크 요청이 병렬로 진행돼 체감 응답성이 올라간다. 콜백·Promise·async-await 패턴이 이벤트 기반 호출을 뒷받침한다.

데이터 교환 레이어에서는 XHR이 넓은 호환성을, Fetch가 단순한 API와 스트리밍·Abort 지원을 제공한다. 다만 브라우저별로 지원 범위가 다를 수 있어 확인이 필요하다. Same-Origin Policy, CORS, Credentials 포함 여부 같은 보안·정책 고려도 빠질 수 없다.

포맷 선택에서는 XML이 스키마·변환(XSLT)에 강점이 있고, JSON은 경량이라 파싱 비용과 바이트 수가 줄어든다. HTML Fragment는 서버 렌더링 조각을 재사용하기엔 좋지만 클라이언트 조작의 유연성은 떨어진다.

DOM 업데이트 전략도 설계 지점이다. 최소 변경(diff)과 배치 업데이트(requestAnimationFrame, microtask)를 적용하고, 대량 리스트는 가상 스크롤·윈도잉을 고려한다. 로딩 상태, 낙관적 업데이트, 에러 롤백 같은 UI 상태관리도 함께 필요하다.

요소 기술 스택은 XHTML/DHTML/CSS/JavaScript/DOM, XML/XSLT, JSON, XMLHttpRequest로 이어지며, 현대 환경에서는 Fetch API와 모듈 번들러·Vite 같은 도구와 조합해 쓴다.

실무에서 자주 만나는 패턴

검색 자동완성은 키 입력을 200~300ms 디바운스한 뒤 쿼리를 보내고 제안어 리스트를 부분 렌더링한다. 이전 요청을 무효화하는 토큰·시퀀스 관리가 함께 필요하다.

지역·카테고리 같은 의존 선택 컴포넌트는 상위 선택값이 바뀌면 하위 옵션을 비동기로 갱신한다. 키 기반 캐싱과, 오류 시 이전 값을 유지하는 전략을 함께 둔다.

폼 유효성 검증과 저장에서는 서버 측 중복·규칙 검증을 Ajax로 먼저 확인하고, 저장 시에는 낙관적 UI와 실패 시 롤백을 처리한다.

무한 스크롤·페이지네이션은 뷰포트 근접 시 다음 페이지를 프리페치하며, 중복 요청 방지와 마지막 페이지 감지가 필요하다.

대시보드 갱신은 폴링·롱폴링·SSE(WebSocket 대안) 중에서 선택한다. SSE·스트리밍의 브라우저·프록시 호환성은 항목별로 최신 정보를 확인해야 한다.

파일 업로드는 XHR의 onprogress로 진행률을 보여주고, 대용량은 분할 업로드와 재시도·합치기 API를 설계한다.

놓치면 안 되는 체크리스트

보안에서는 CORS를 Origin·메서드·헤더 단위로 최소 허용하고, CSRF는 SameSite 쿠키나 토큰으로, XSS는 콘텐츠 인코딩과 신뢰된 데이터만 DOM에 주입하는 방식으로 방어한다. 신뢰성 확보에는 타임아웃·재시도(지수 백오프, 재시도 한도, 멱등 엔드포인트)와 Idempotency-Key를 통한 중복 제출 방지가 필요하다. 성능 측면에서는 서버 캐시(Etag/Last-Modified), 클라이언트 캐시(Cache-Control), 압축(gzip/br), JSON 필드 최소화로 페이로드를 경량화한다. 일관성을 위해서는 응답 스키마를 버저닝(v1/v2)하고 에러를 표준화(code/message)하며 OpenAPI로 클라이언트-서버 계약을 문서화한다. 동시성 문제는 요청 시퀀스 관리와 AbortController를 통한 이전 요청 취소로 경쟁 상태를 방지한다.

포맷별로 비교하면

포맷 성능 확장성 일관성 안정성 운영 편의
XML 상(스키마/XPath/XSLT) 중(무겁고 도구 의존)
JSON 상(경량) 중(스키마 부재 시 문서화 필요) 상(에코시스템 풍부)
HTML Fragment 상(즉시 렌더) 중(서버-클라 결합도 상승)

실제로 얻는 것

응답성은 부분 갱신을 적용했을 때 사용자 체감 응답시간이 3060% 단축된다(네트워크·DOM 비용 절감을 전제로 한다). 트래픽은 JSON 기반 페이로드가 전체 HTML 대비 5090% 전송량을 줄이는 사례가 일반적이다. 서버 부하는 조건부 요청·캐시 적중률이 올라가면 애플리케이션 CPU 사용량이 10~30% 절감될 것으로 기대된다. UX 품질 면에서는 인터랙션 연속성이 유지되고 작업 맥락이 보존되며, 오류가 국소화돼 과업 완료율이 올라간다. 개발 생산성 측면에서는 명확한 API 계약과 재사용 가능한 Ajax 유틸을 도입하면 화면 추가·변경 리드타임이 줄어든다.

Ajax의 본질은 비동기 통신과 부분 렌더링으로 사용자 경험을 최적화하는 데 있다. 현대 웹에서는 JSON+Fetch를 기본으로 하되, 요구사항에 따라 XML/XSLT나 HTML Fragment를 전략적으로 섞어 쓴다. 보안(CORS/CSRF/XSS), 신뢰성(타임아웃/재시도/멱등성), 성능(캐싱/압축/경량화)을 체계화한 표준 패턴을 먼저 세우고, 신규 도입 시에는 공통 Ajax 유틸·에러 표준화·캐싱 정책부터 정립하는 순서가 일관된 아키텍처로 이어진다.

AjaxXMLHttpRequestFetch API비동기 통신CORS