Javascript

AbortController, 아직 제대로 모르세요? - 숨겨진 기능부터 활용 꿀팁까지!

드리프트2 2025. 3. 22. 17:59

AbortController, 아직 제대로 모르세요? - 숨겨진 기능부터 활용 꿀팁까지!

 

개발자라면 AbortController, 다들 한 번쯤 들어보셨을 텐데요.

Fetch (fetch) 요청 취소하는 기본적인 기능만 알고 계신 분들이 많을 거예요.

하지만 AbortController는 생각보다 훨씬 강력한 기능을 숨겨놓고 있다는 사실, 알고 계셨나요?

Fetch (fetch) 요청 취소는 기본이고, 이벤트 리스너 (event listener) 관리, React Hooks (리액트 훅)에서도 유용하게 쓸 수 있다는데요.

혹시 AbortController의 진짜 힘, 제대로 알고 계신가요?

지금부터 AbortController의 숨겨진 기능과 활용법을 하나씩 파헤쳐 볼까요?

  1. AbortController로 Fetch (fetch) 요청 취소하기: 기본 중의 기본!

Fetch (fetch) 요청 취소할 때 AbortController를 사용하는 건 워낙 유명하죠.

가장 기본적인 사용법부터 짚고 넘어갈게요.

다음 예제는 AbortController를 사용해서 취소 가능한 Fetch (fetch) 요청을 만드는 방법을 보여줍니다.

fetchButton.onclick = async () => {
  const controller = new AbortController();
  // 취소 버튼 추가
  abortButton.onclick = () => controller.abort();
  try {
    const response = await fetch('/json', { signal: controller.signal });
    const data = await response.json();
    // 비즈니스 로직 (business logic) 처리
  } catch (error) {
    const isUserAbort = error.name === 'AbortError';
    // AbortError는 AbortController (어볼트컨트롤러)로 요청이 취소되었을 때 발생합니다.
  }
};

 

위 예시 코드는 AbortController가 없었다면 불가능했을 기능을 보여주는데요.

바로 프로그램적으로 네트워크 요청을 취소하는 기능입니다.

요청이 취소되면 브라우저 (browser)는 Fetch (fetch) 작업을 멈춰서 네트워크 대역폭을 아낄 수 있습니다.

중요한 점은, 요청 취소가 꼭 사용자 액션 (action)에 의해서만 가능한 게 아니라는 거예요.

controller.signal은 AbortSignal (어볼트시그널) 객체를 제공하는데요.

이 객체를 통해서 Fetch (fetch) 같은 비동기 작업과 소통하면서 작업 취소를 가능하게 해줍니다.

마치 리모컨처럼 비동기 작업을 제어할 수 있는 거죠.

여러 개의 시그널 (signal)을 하나로 합쳐서 사용하고 싶을 때는 AbortSignal.any()를 사용하면 됩니다.

사용법은 다음과 같습니다.

try {
  const controller = new AbortController();
  const timeoutSignal = AbortSignal.timeout(5000); // 5초 타임아웃 (timeout) 시그널 (signal)
  const response = await fetch(url, {
    // 시그널 (signal) 중 하나라도 트리거 (trigger)되면 Fetch (fetch)를 중단합니다.
    signal: AbortSignal.any([controller.signal, timeoutSignal]),
  });
  const data = await response.json();
} catch (error) {
  if (error.name === 'AbortError') {
    // 사용자에게 취소 알림
  } else if (error.name === 'TimeoutError') {
    // 사용자에게 타임아웃 (timeout) 알림
  } else {
    // 네트워크 문제 같은 다른 에러 처리
    console.error(`Type: ${error.name}, Message: ${error.message}`);
  }
}
  1. AbortController  vs. AbortSignal , 뭐가 다를까요?
  • AbortController : controller.abort()를 통해 연결된 시그널 (signal)을 명시적으로 취소할 때 사용합니다. 취소 버튼 역할을 한다고 생각하면 됩니다. 🖲️
  • AbortSignal : 시그널 (signal) 객체 자체를 나타냅니다. 직접적으로 뭔가를 취소할 수는 없고, 취소 상태를 알리는 역할만 합니다. 취소 상태를 감지하는 센서 (sensor) 같은 거라고 생각하면 될 것 같아요. 📡

AbortSignal로는 다음과 같은 작업을 할 수 있습니다.

  • signal.aborted를 사용해서 취소되었는지 여부를 확인할 수 있습니다. signal.abortedtrue이면 취소된 상태이고, false이면 아직 취소되지 않은 상태입니다.
  • abort 이벤트 (event)를 리스닝 (listening)할 수 있습니다. 시그널 (signal)이 취소될 때 abort 이벤트 (event)가 발생합니다.
if (signal.aborted) {
  // 이미 취소된 경우 처리
}
signal.addEventListener('abort', () => {
  // 취소 이벤트 (event) 발생 시 실행할 코드
});

 

AbortController를 사용해서 요청을 취소하면 서버 (server)는 요청 처리를 멈추고 응답을 보내지 않기 때문에, 네트워크 대역폭 낭비를 막고, 불필요한 연결을 줄여서 클라이언트 (client) 성능을 향상시킬 수 있습니다.

  1. AbortController , 어디에 써먹을 수 있을까요?

AbortController는 Fetch (fetch) 요청 취소 외에도 다양한 곳에서 활용될 수 있는데요.

몇 가지 대표적인 Use Case (사용 사례)를 알아볼까요?

    1. WebSocket (웹소켓) 연결 취소

WebSocket (웹소켓) API (응용 프로그래밍 인터페이스)처럼 옛날 API (응용 프로그래밍 인터페이스)들은 AbortSignal (어볼트시그널)을 기본적으로 지원하지 않는데요.

이런 경우에도 AbortController를 사용해서 연결을 취소할 수 있습니다.

다음과 같이 코드를 작성하면 WebSocket (웹소켓) 연결을 취소할 수 있습니다.

function abortableSocket(url, signal) {
  const socket = new WebSocket(url);
  if (signal.aborted) {
    socket.close();
    // 이미 취소된 경우 즉시 중단
  }
  signal.addEventListener('abort', () => socket.close());
  return socket;
}

 

참고: AbortSignal 이 이미 취소된 상태라면 abort 이벤트 (event)가 바로 발생하지 않을 수 있습니다.

그래서 미리 signal.aborted 상태를 확인하고 처리해줘야 합니다.

    1. 이벤트 리스너 (event listener) 제거

원래 이벤트 리스너 (event listener)를 제거하려면 removeEventListener 함수에 addEventListener에 사용했던 것과 똑같은 함수 레퍼런스 (reference)를 전달해야 했는데요.

코드를 한번 볼까요?

window.addEventListener('resize', () => doSomething());
window.removeEventListener('resize', () => doSomething()); // 이렇게 하면 안 됩니다!

 

위 코드처럼 익명 함수를 사용하면 removeEventListener가 제대로 작동하지 않습니다.

하지만 AbortController를 사용하면 훨씬 간단하게 이벤트 리스너 (event listener)를 제거할 수 있습니다.

const controller = new AbortController();
const { signal } = controller;
window.addEventListener('resize', () => doSomething(), { signal });
// abort()를 호출하여 이벤트 리스너 (event listener) 제거
controller.abort();

 

이렇게 addEventListener의 options (옵션)에 signal을 넣어주면, controller.abort() 호출 한 번으로 이벤트 리스너 (event listener)를 깔끔하게 제거할 수 있습니다.

정말 편리하죠?

혹시 옛날 브라우저 (browser)를 지원해야 한다면 AbortController (어볼트컨트롤러) Polyfill (폴리필)을 추가하는 것을 고려해보세요.

    1. React Hooks (리액트 훅)에서 비동기 작업 관리

React (리액트)에서 useEffect 훅 (hook)을 사용할 때, 이전 비동기 작업이 끝나기 전에 컴포넌트 (component)가 업데이트 (update)되면 의도치 않게 비동기 작업이 동시에 여러 번 실행될 수 있습니다.

다음 코드를 한번 볼까요?

function FooComponent({ something }) {
  useEffect(async () => {
    const data = await fetch(url + something);
    // 데이터 처리
  }, [something]);
  return <></>;
}

 

이런 문제를 막으려면 AbortController를 사용해서 이전 작업을 취소해주면 됩니다.

function FooComponent({ something }) {
  useEffect(() => {
    const controller = new AbortController();
    const { signal } = controller;
    (async () => {
      const data = await fetch(url + something, { signal });
      // 응답 처리
    })();
    return () => controller.abort(); // cleanup (정리) 함수에서 abort() 호출
  }, [something]);
  return <></>;
}

 

useEffect 훅 (hook) cleanup (정리) 함수에서 controller.abort()를 호출하면 컴포넌트 (component)가 unmount (언마운트)되거나 업데이트 (update)되기 전에 이전 Fetch (fetch) 요청을 취소할 수 있습니다.

이렇게 하면 불필요한 비동기 작업 중복 실행을 막고, 컴포넌트 (component) 성능을 최적화할 수 있습니다.

    1. Node.js (노드js) 환경에서 AbortController 사용

최신 Node.js (노드js) 버전에서는 AbortController와 호환되는 setTimeout 구현을 제공하는데요.

node:timers/promises 모듈에서 setTimeout을 불러와서 사용하면 됩니다.

const { setTimeout: setTimeoutPromise } = require('node:timers/promises'); // node:timers/promises 모듈에서 setTimeout 불러오기
const controller = new AbortController();
const { signal } = controller;

setTimeoutPromise(1000, 'foobar', { signal }) // AbortSignal (어볼트시그널)과 함께 setTimeoutPromise 사용
  .then(console.log)
  .catch((error) => {
    if (error.name === 'AbortError') console.log('Timeout was aborted'); // AbortError 처리
  });

controller.abort(); // 타임아웃 (timeout) 취소

 

브라우저 (browser) setTimeout과 다르게, Node.js (노드js) setTimeout 구현은 콜백 (callback) 함수를 인자로 받지 않고, Promise (프로미스) 기반으로 작동합니다.

그래서 .then()이나 await를 사용해서 결과를 처리해야 합니다.

    1. 고급 스케줄링을 위한 TaskController

브라우저 (browser)는 작업 우선순위 (priority)를 조절하기 위해 scheduler.postTask() API (응용 프로그래밍 인터페이스)를 도입하고 있고, TaskController는 AbortController (어볼트컨트롤러)를 확장한 기능인데요.

TaskController를 사용하면 작업을 취소하거나 작업 우선순위를 동적으로 조절할 수 있습니다.

const taskController = new TaskController();
scheduler
  .postTask(() => console.log('작업 실행'), { signal: taskController.signal }) // TaskController (태스크컨트롤러) 시그널 (signal)과 함께 postTask 사용
  .then((result) => console.log(result))
  .catch((error) => console.error('Error:', error));

taskController.abort(); // 작업 취소

 

작업 우선순위 제어가 필요 없다면 AbortController만 사용해도 충분합니다.

  1. 결론

AbortController, 이제 더 이상 Fetch (fetch) 요청 취소 기능에만 국한된 존재가 아니라는 거, 확실히 아셨겠죠?

현대 JavaScript 개발에서 AbortController는 비동기 작업을 관리하고 취소하는 데 없어서는 안 될 필수 도구입니다.

브라우저 (browser) 환경뿐만 아니라 Node.js (노드js) 환경까지 지원하면서 AbortController의 활용도는 점점 더 높아지고 있는데요.

혹시 아직 AbortController를 제대로 모르고 있었다면, 이번 기회에 AbortController의 모든 기능을 섭렵하고 비동기 프로그래밍 toolbox (툴박스)에 꼭 추가해보세요!

분명 개발 효율을 훨씬 더 높여줄 겁니다.