AbortController로 fetch 요청 취소하기

제제 🍊·2023년 7월 31일
0

들어가며

API를 사용하는 기능 중에는 API의 응답을 받기까지 너무 긴 시간을 필요로 하는 기능이 존재한다. 이럴 상황에는 사용성 향상을 위해 pending 상태에는 스켈레톤을 보여주거나 optimistic update를 구현할 수 있을 것이다. 백엔드한테 “빠르게 해주세요”라고도 할 수도 있겠지만, 동기가 아닌 비동기의 한계에 부딪친다는 점이 참으로 아쉽다.

위와 같이 스켈레톤을 보여주거나, optimistic update 처리를 하는 방법도 있겠지만 유저가 요청을 직접 취소하게 함으로써 노이즈 함을 없애는 방법도 있을 것이다. 이게 무슨 소린가 싶다면, OpenAI의 Stop generate 기능을 떠올려보자.

오늘 정리하고자 하는 “AbortController를 통한 fetch API 요청 취소하기”의 맥락과는 다소 차이가 존재하지만, 큰 맥락에서는 궤를 같이한다고 볼 수 있다. 이 밖에도 크기가 큰 파일을 내려받을 때 요청을 취소하고 경로를 이동하는 등의 예시를 들 수 있다.

AbortController와 fetch API

fetch API의 명세는 다음의 문서를 확인하자.
https://developer.mozilla.org/ko/docs/Web/API/Fetch_API/Using_Fetch

기본적으로 fetch API를 통해 GET 요청을 보낸다고 하면 다음과 같이 사용할 수 있다.

fetch('https://example.com', {
	method: 'GET',
	headers: {
		'Content-Type': 'application/json',
	},
})

AbortController의 명세는 다음의 문서를 확인하자.
https://developer.mozilla.org/ko/docs/Web/API/AbortController

"AbortController 인터페이스는 하나 이상의 웹 요청을 취소할 수 있게 해준다."는 문서의 내용처럼, AbortController와 함께 fetch API의 signal 옵션 프로퍼티를 활용하면, fetch API의 요청을 직접 핸들링할 수 있게 된다.

예시를 들어보자면 다음과 같다.

// AbortController 인스턴스 생성
const controller = new AbortController();

fetch('https://example.com/', {
	method: 'GET',
	headers: {
		'Content-Type': 'application/json',
	},
	signal: controller.signal // signal 프로퍼티에 AbortController 인스턴스 전달
})

그리고 이렇게 생성된 controller를 유저의 특정 액션과 연결해서 controller.abort()를 호출해주면, 요청을 취소할 수 있다.

참고
직접 실험해본 결과, abort()가 호출되었을 때 fetch 요청이 pending 상태인지 fetching 상태인지에 따라 양상이 다르게 펼쳐진다.

pending 상태에서는 에러가 발생하지 않는다. fetch가 반환하는 Promise의 value값이 undefined가 된다.

fetching 상태에서는 에러가 발생한다. 에러 객체(error)의 이름(error.name)을 확인해보면 AbortError가 발생했음을 확인할 수 있다.

fetch API의 timeout 설정하기

fetch API가 pending 시간이 일정 시간을 초과할 때 abort 처리를 하고 싶다면 어떻게 하면 될까? 즉, 유저의 특정 액션에 abort()를 호출하는 것이 아니라, fetch API의 pending 상태가 길어질 때 정상적으로 답변을 주기 어렵다고 판단하고 abort 처리를 하는 것이다.

new Promise((resolve) => {
    const controller = new AbortController();
    const timeoutId = setTimeout(() => {
      controller.abort();
    }, 5000); // 최대 5초 pending

    fetch('https://example.com', { ...options, signal: controller.signal })
      .then((response) => {
        clearTimeout(timeoutId);
        resolve(response);
      })
      .catch(() => {
        clearTimeout(timeoutId);
        resolve({ ok: false });
      });
  })

코드를 살펴보자.

우선 위에서 설명한 것과 같이 fetch API의 signal 프로퍼티로 AbortController 인스턴스를 설정했다.

그리고 fetch API가 호출되기 직전, fetch API에 연결된 AbortController가 abort()를 호출하는 타이머가 시작되는데 이 타이머는 5초 뒤에 동작하게 된다.

즉, fetch API가 5초 안에 response를 뱉어내지 않으면 타이머가 동작하여 abort()를 호출함으로써 fetch API를 취소시키고, fetch API가 5초 안에 response를 뱉어내면 타이머를 제거함으로써 abort() 호출을 막게 된다.

이를 유틸화하여 사용하면 timeout이 적용된 fetch API를 만들 수 있고, 나아가 fetch API의 retry 로직에도 활용할 수 있을 것이다.

마무리

정확한 인터페이스와 객체의 특성을 파악하지 못한 채 정리 글을 작성하게 된 것이 아쉽다. Next.js 13에서도 fetch API를 적극 사용하게 된만큼 fetch API에 대해서도 깊이 이해하면 좋을 듯하다.

profile
블로그 꿈나무 🌱

2개의 댓글

comment-user-thumbnail
2023년 7월 31일

감사합니다. 이런 정보를 나눠주셔서 좋아요.

답글 달기
comment-user-thumbnail
2023년 8월 1일

재밌게 읽고 갑니다~

답글 달기