개발 기술/개발 이야기

[JS] AbortController로 웹 요청 취소하기(비동기, dom 이벤트)

by GicoMomg 2023. 2. 26.

💡 이번 시간에는 웹 요청을 중단할 수 있는 AbortController에 대해 알아보았다!


1. AbortController는 무엇일까?

1) AbortController란

  • AbortController는 하나 이상의 웹 요청을 중단할 수 있는 인터페이스이다.
  • 대표적으로 fetch API, Dom Event를 특정 시점에 중단시킬 수 있다.

2) AbortController API

(1) new AbortController()

  • 웹 요청을 중단할 수 있는, AbortController 인터페이스를 생성한다.
const controller = new AbortController();

(2) AbortController.signal

  • 요청을 취소할 수 있는 AbortSignal 객체 인터페이스를 반환한다.
const controller = new AbortController();
const signal = controller.signal;

  • 웹 요청 API에 AbortSignal객체의 signal값을 인자로 넘기면, 요청을 취소할 수 있는 상태가 된다.
const controller = new AbortController();
const signal = controller.signal;

// fetch 요청을 취소할 수 있는 상태
const response = await fetch(url, { signal });  // signal를 넘겨줌

  • dom event에도, signal을 인자로 넘기면 이벤트를 취소할 수 있는 상태가 된다.
const controller = new AbortController();
const signal = controller.signal;

// dom event를 취소할 수 있는 상태
button.addEventListener('click', onClick, { signal }); // signal를 넘겨줌

(3) AbortController.abort()

  • abort() 를 사용하면, signal가 할당된 웹 요청을 취소할 수 있다.
const controller = new AbortController();

controller.abort();

  • 아래 코드는 abort()를 호출해 버튼의 클릭 이벤트를 취소한 예시이다.
const controller = new AbortController();

button.addEventListener('click', onClick, { signal });  // singal이 인자로 할당됨
controller.abort();  // 호출시, button의 click 이벤트를 취소

(4) abortSignal.aborted

  • abortedabort()가 실행되어 요청이 취소되었는지 여부를 알 수 있다.
const isAborted = abortSignal.aborted;

  • 아래 코드는 aborted 여부에 따라 서로 다른 콘솔을 출력하는 예시이다.
const controller = new AbortController();
const signal = controller.signal;

// ...

if (signal.aborted) console.log('요청 취소'); // (1)
else console.log('요청 취소되지 않음!');        // (2)
  • 만약 abort()가 실행되어 요청이 취소되었다면, abortedtrue이므로 ‘요청 취소’를 출력한다. - (1)
  • 그러나 요청이 취소되지 않았다면 ‘요청 취소되지 않음!’을 출력한다. - (2)

(5) abort event

abort이벤트는 abort()가 실행되어 요청이 중단된 경우 발생한다.

// 이벤트 접근 방법1
addEventListener('abort', (event) => { ... });

// 이벤트 접근 방법2
onabort = (event) => { ... };



3) AbortController 활용법

🤜 앞서 우리는 AbortController는 무엇이며, 어떤 변수와 이벤트가 있는지 알아보았다.
그렇다면 AbortController은 어떤 상황에서 활용하면 좋을까? 여러 예시와 함께 살펴보자

(1) 비동기 처리 중간에 멈추기

  • 비동기의 경우 중간에 호출을 멈추기 어렵다.
  • 하지만 어떤 비동기 동작을 요청했는데 너무 오랜 시간이 걸려 취소해야한다면, 방법이 없을까?
  • 이때 바로 AbortController를 사용하는 것이다!
  • AbortController를 사용하면, 비동기 처리를 중간에 멈출 수 있다.

  • 아래 예시에는 사과 이모티콘을 요청하는 버튼과 요청을 취소하는 버튼이 있다.
  • 사과 요청하기 버튼은 사과 이모티콘을 화면에 띄우는 비동기 함수를 호출한다.
  • 사과 이모티콘의 경우 요청 후 2초가 지나야 화면에 나타나는데, 취소 버튼을 누르면 비동기 처리를 중간에 취소할 수 있다.

See the Pen AbortController by KumJungMin (@kumjungmin) on CodePen.



비동기 취소 코드는 어떻게 구성되어 있을까? 하나씩 살펴보자

  • 우선 new AbortController를 사용해 AbortController 객체를 만든다.
const controller = new AbortController();

  • 그 다음으로 중요한 건, 사과 이모티콘을 요청하는 비동기 함수requestAppleIcon()이다.

    function requestAppleIcon({ signal }) {
    return new Promise(( resolve, reject ) => {
      if (signal.aborted) {  // (1)
            reject(new DOMException('Aborted', 'AbortError'));
        } else {
        setTimeout(() => {   // (2) 
          resolve('🍎 🍎 🍎 🍎');
        }, 2000);
      }
      signal.addEventListener('abort', () => { // (3)
        reject(new DOMException('Aborted', 'AbortError')); 
      });
    });
    }
  • signal.abortedtrue라는 건, 이미 취소 요청이 있었다는 의미로 이때는 에러를 반환한다. - (1)

  • signal.abortedfalse라면 정상적으로 사과 이모티콘을 반환한다. - (2)

  • 함수 실행 도중에 abort 이벤트가 발생하면 에러를 반환한다. - (3)


  • requestFunc()은 사과 요청하기 버튼 클릭시 실행하는 함수이다.
async function requestFunc() {
  try {
    const data = await requestAppleIcon({ signal: controller.signal }); // (4)
    result.innerText = data;
  } catch(e) {
    if (e.message === 'Aborted') result.innerText = '중단되어 출력 불가 🙅‍♂️'; // (5)
  }
}
  • requestAppleIcon()controllersignal을 인자로 넘기면 요청을 중단할 수 있는 상태가 된다. - (4)
  • 실행 중단 요청이 발생할 경우, 에러를 반환한다. - (5)

  • abortFunc()은 요청 중단하기 버튼 클릭시 실행하는 함수이다.
    function abortFunc() {
    controller.abort();          // (6)
    }
  • controller.abort()를 실행하며 요청을 중단시킬 수 있다. - (6)

(2) dom 이벤트 한 번만 발생시키기

  • dom 이벤트의 경우, once로 지정하면 이벤트를 한 번만 실행시킬 수 있다.
el.addEventListener('click', function() {
  alert('안녕하세요!!');
}, { once : true });

그런데 AbortController를 사용해서도 dom이벤트를 한 번만 실행시킬 수 있다!

const button = document.getElementById('button');

const controller = new AbortController();
const { signal } = controller;

button.addEventListener('click', onClick, { signal });  // (1)

function onClick() {
  console.log('clicked!');
  controller.abort();       // (2)
}
  • 방법은 간단한데, 우선 한 번만 실행하고 싶은 이벤트에 signal을 인자로 넘긴다. - (1)
  • 그 다음 이벤트 발생시 controller.abort()를 실행하면 이벤트를 한 번만 발동시킬 수 있다. - (2)

이렇게 코드를 작성하면 버튼을 눌러도 최초 1번만 실행된다 :)

See the Pen Event once by KumJungMin (@kumjungmin) on CodePen.



(3) 이벤트 한 번에 제거하기, removeEventListener의 대안

  • 이벤트 리스너의 경우, removeEventListener를 하지 않으면 이벤트 리스너가 요소보다 오래 남아 메모리 누수가 발생한다.
  • 그렇기에 우리는 이벤트 리스너를 removeEventListener를 사용해 제거해줘야 한다.
  • 그런데 여러 개의 이벤트 리스너가 있는 경우, 일일이 다 removeEventListener로 제거해야하나?
  • 만약 AbortController를 몰랐다면 아래와 같이 제거해야 했을 것이다.
// 이벤트 리스너 등록
btn1.addEventListener('click', onClick);
btn2.addEventListener('click', onClick);
btn3.addEventListener('click', onClick);

// 이벤트 리스너 제거
window.addEventListener('beforeunload', () => {
    btn1.removeEventListener('click', onClick);
    btn2.removeEventListener('click', onClick);
    btn3.removeEventListener('click', onClick);
    };
)

  • 하지만 AbortController를 사용하면, abort()를 호출하여 한 번에 이벤트 리스너를 제거할 수 있다!
// AbortController 객체 가져오기
const controller = new AbortController();
const { signal } = controller;

// signal인자를 넘기기
btn1.addEventListener('click', onClick, { signal });
btn2.addEventListener('click', onClick, { signal });
btn3.addEventListener('click', onClick, { signal });

// abort() 호출해서 이벤트 리스너 제거하기
window.addEventListener('beforeunload', () => {
    controller.abort();
    };
)



이번 시간에는 AbortController를 사용해 웹 요청을 취소하는 방법에 대해 알아보았다!
우선 취소하고 싶은 웹 요청 함수에 singal변수를 인자로 넘겨야 했으며,
abort()를 실행하면 요청을 취소할 수 있었다 :)
만약 비동기 함수를 중간에 취소해야 하거나 Dom 이벤트를 한 번에 제거하고 싶다면 이 방법을 써도 좋을 듯 하다~
추가적으로 더 알아보고 싶다면, CSS-trick의 포스팅, (MDN Web Docs 문서)도 살펴보자

반응형

댓글