AbortController는 비동기 작업을 취소 할 수 있는 표준 API입니다. fetch 요청 취소가 가장 대표적이지만, 이벤트 리스너 제거, 타이머 취소, 심지어 여러 작업을 한 번에 취소하는 데도 활용할 수 있습니다.

기본 사용법

JS
// 1. 컨트롤러 생성
const controller = new AbortController();
const signal = controller.signal;

// 2. signal을 전달
fetch("/api/data", { signal })
  .then((response) => response.json())
  .then((data) => console.log(data))
  .catch((err) => {
    if (err.name === "AbortError") {
      console.log("요청이 취소되었습니다.");
    } else {
      console.error("에러:", err);
    }
  });

// 3. 취소
controller.abort();

fetch 취소 — 가장 흔한 활용

React에서의 cleanup

JSX
useEffect(() => {
  const controller = new AbortController();

  async function fetchData() {
    try {
      const response = await fetch("/api/users", {
        signal: controller.signal,
      });
      const data = await response.json();
      setUsers(data);
    } catch (err) {
      if (err.name !== "AbortError") {
        setError(err);
      }
    }
  }

  fetchData();

  // 컴포넌트 언마운트 시 취소
  return () => controller.abort();
}, []);

검색 자동완성 — 이전 요청 취소

JS
let currentController = null;

async function search(query) {
  // 이전 요청 취소
  if (currentController) {
    currentController.abort();
  }

  currentController = new AbortController();

  try {
    const response = await fetch(`/api/search?q=${query}`, {
      signal: currentController.signal,
    });
    return await response.json();
  } catch (err) {
    if (err.name === "AbortError") {
      return null; // 취소된 요청은 무시
    }
    throw err;
  }
}

타임아웃 패턴

AbortSignal.timeout (모던 브라우저)

JS
// 5초 후 자동 취소
try {
  const response = await fetch("/api/slow", {
    signal: AbortSignal.timeout(5000),
  });
  const data = await response.json();
} catch (err) {
  if (err.name === "TimeoutError") {
    console.log("5초 타임아웃!");
  }
}

수동 타임아웃 구현

JS
function fetchWithTimeout(url, options = {}, timeout = 5000) {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeout);

  return fetch(url, {
    ...options,
    signal: controller.signal,
  }).finally(() => clearTimeout(timeoutId));
}

이벤트 리스너 자동 제거

addEventListenersignal 옵션으로 이벤트 리스너를 자동 제거할 수 있습니다.

JS
const controller = new AbortController();

// signal로 등록한 이벤트 리스너
window.addEventListener("resize", handleResize, {
  signal: controller.signal,
});
window.addEventListener("scroll", handleScroll, {
  signal: controller.signal,
});
document.addEventListener("click", handleClick, {
  signal: controller.signal,
});

// 한 번에 모든 리스너 제거!
controller.abort();
// removeEventListener를 각각 호출할 필요 없음

실전 — 모달 열기/닫기

JS
function openModal() {
  const controller = new AbortController();
  const { signal } = controller;

  // 모달 관련 이벤트 등록
  document.addEventListener("keydown", (e) => {
    if (e.key === "Escape") closeModal();
  }, { signal });

  overlay.addEventListener("click", closeModal, { signal });

  function closeModal() {
    modal.remove();
    controller.abort(); // 모든 이벤트 리스너 자동 제거
  }
}

AbortSignal.any — 여러 신호 조합

JS
// 타임아웃 또는 사용자 취소 중 먼저 발생하는 것
const userController = new AbortController();

const signal = AbortSignal.any([
  AbortSignal.timeout(10000),      // 10초 타임아웃
  userController.signal,           // 사용자 취소 버튼
]);

fetch("/api/data", { signal });

// 취소 버튼 클릭 시
cancelButton.onclick = () => userController.abort();

abort 이유 전달

JS
const controller = new AbortController();

controller.signal.addEventListener("abort", () => {
  console.log("취소 이유:", controller.signal.reason);
});

// 이유를 전달하며 취소
controller.abort("사용자가 페이지를 떠남");
// "취소 이유: 사용자가 페이지를 떠남"

// AbortSignal.timeout은 자동으로 TimeoutError를 이유로 설정

커스텀 비동기 작업 취소

JS
// AbortSignal을 지원하는 커스텀 함수
function delay(ms, { signal } = {}) {
  return new Promise((resolve, reject) => {
    // 이미 취소된 경우
    if (signal?.aborted) {
      reject(new DOMException("Aborted", "AbortError"));
      return;
    }

    const timeoutId = setTimeout(resolve, ms);

    // 취소 시 타이머 정리
    signal?.addEventListener("abort", () => {
      clearTimeout(timeoutId);
      reject(new DOMException("Aborted", "AbortError"));
    });
  });
}

// 사용
const controller = new AbortController();

delay(5000, { signal: controller.signal })
  .then(() => console.log("5초 경과"))
  .catch((err) => {
    if (err.name === "AbortError") console.log("취소됨");
  });

// 2초 후 취소
setTimeout(() => controller.abort(), 2000);

주의사항

JS
// 1. 한번 abort()하면 재사용 불가
const controller = new AbortController();
controller.abort();
console.log(controller.signal.aborted); // true — 영구적

// 새 요청에는 새 컨트롤러가 필요
const newController = new AbortController();

// 2. AbortError 구분
try {
  await fetch(url, { signal });
} catch (err) {
  if (err.name === "AbortError") {
    // 의도된 취소 — 에러로 처리하지 않음
    return;
  }
  // 진짜 에러
  throw err;
}

활용 요약

패턴설명
fetch 취소컴포넌트 언마운트, 검색 자동완성
타임아웃AbortSignal.timeout(ms)
이벤트 리스너 정리addEventListener({ signal })
여러 작업 일괄 취소하나의 controller로 여러 작업 관리
조건부 취소AbortSignal.any([...signals])

**기억하기 **: AbortController는 "비동기 작업의 취소 버튼"입니다. fetch뿐 아니라 이벤트 리스너, 타이머, 커스텀 비동기 작업까지 통합 관리할 수 있습니다. React의 useEffect cleanup에서 fetch를 취소하는 패턴은 실무 필수입니다.

댓글 로딩 중...