[js] fetch 네트워크 요청 취소

10527 단어 Fetch네트워크Fetch

이 글은 디바운싱과 쓰로틀링에서 이어진다.

앞에서 디바운싱으로 해결될 듯했던 문제는 해결되지 않았다. 디바운싱을 적용했음에도 왜 같은 오류가 발생하고 있는 걸까?
그것은 이벤트가 여러 번 발생했을 때 네트워크 응답이 도착하는 순서가 다른 것이 문제인데 디바운싱을 해도 이벤트는 여러 번 발생할 수 있기 때문이다.

위 gif를 보면 아이폰이라고 단어가 입력되고 그에 관련된 자동 완성 리스트가 출력이 되었다. 그런데 단어를 치는 중 나도 모르게 을 치고 잠깐 멈칫했다. 근데 그 찰나가 500ms를 넘었던 모양이다. 그래서 을 치고 이벤트는 발생을 해버렸고 아이폰이라고 입력을 완료한 뒤 이벤트도 발생해서 총 두 번 이벤트가 발생한 것이다.
그런데 문제는 여기서 아이폰입력 이벤트의 fetch 응답보다 입력 이벤트의 fetch 응답이 더 늦게 도착한 것이었다.

이제 문제의 원인이 이벤트가 아니라는 것을 알게 되었다. 문제는 이벤트가 아니라 이벤트 발생 후 실행된 fetch의 네트워크 응답이 요청 순서대로 온다는 것을 보장받을 수 없다는 것이 원인이었다.

그럼 어쩌지...

처음에는 순서를 보장받을 수 있는 방법이 있는지 찾아봤다. 그런데 검색 결과는 대부분 a, b, c라는 세 함수를 비동기로 실행할 때 순서를 보장받는 방법에 대한 내용이어서 내 문제에 대한 해결법이 아니었다. 내가 못 찾은 순서를 보장받는 방법이 있을 수 있지만 어쨌든 난 못 찾았다.

그러다 생각난 게 먼저 간 요청을 취소하면 되지 않나?라는 것이었다. 네트워크 요청 취소를 검색해서 방법을 찾았다.

AbortController

fetch request의 처리를 취소하기 이 글이 처음 발견한 글인데 딱 보고 지금 내 문제의 해결법이라는 생각이 들었다.

AbortController를 사용하면 fetch로 보낸 네트워크 요청을 취소할 수 있다. MDN을 보면 다음과 같이 설명하고 있다.

인터페이스는 AbortController원할 때 하나 이상의 웹 요청을 중단할 수 있는 컨트롤러 객체를 나타냅니다.

번역이... 이상하다....아무튼 중단할 수 있단다.
사용법은 생각보다 어렵지 않다.

  • 새로운 const abortController = new AbortController()를 생성
  • fetch 옵션에 signal: abortController.signal 추가
  • 요청 취소를 원하는 타이밍에 abortController.abort() 실행

지금 나는 fetch를 사용하는 부분이 자동 완성 검색어 뿐이 지만 프로젝트가 커지면 fetch를 사용하는 클래스가 많아질 테니 그 클래스마다 호출해서 사용하는 것이 좋을 것 같았다.
그래서 AbortController를 속성으로 가지는 FetchController라는 객체를 만들었다.

FetchController

class FetchController {
  constructor() {
    this.abortController = null;
    this.isPending = false;
  }

  fetch(url) {
    //isPending으로 이전 네트워크 요청의 상태를 확인하고
    //isPending이 true면 abort시키고 아니면 새로운 요청을 한다
    if (this.isPending) {
      this.abortController.abort();
    }
    //AbortController는 일회성이라 fetch를 실행하는 함수 안에서 생성해줘야 한다.
    this.abortController = new AbortController();
    this.isPending = true;

    return (
      fetch(url, { signal: this.abortController.signal })
        .then(res => res.json())
        .finally(() => {
          this.isPending = false;
        })
    );
  }
  //요청을 취소해야하는 타이밍에 이 abort()를 사용한다.
  abort() {
    this.abortController.abort();
  }
}

export { FetchController };

사용하기

//fetcher라는 말을 쓰나? 그냥 예시로 쓰려고 대충 만들었다
class AutoCompleteFetcher {
	constructor() {
        //FetchController 인스턴스를 생성한다
		this.autoCompleteFetchController = new FetchController();
	}
  
	//자동 완성 데이터를 요청하는 함수
	fetchAutoCompleteSearchTerms() {
      	const url = `https://블라블라`;
      
      	//이 fetch는 fetchController 클래스의 새로 만든 fetch
        return this.autoCompleteFetchController
          .fetch(url)
          .then('fetch하고나서 할 동작'));
        }

	//fetch 후 동작할 함수이자 abort가 실행되어야 할 타이밍의 어떤 함수
	abortTimingFunction() {
		this.abortController.abort();
	}
}

문제를 해결하고 나니 타이핑을 칠 때는 에러가 잘 안 나서 지울 때로 영상을 찍었다. 영상처럼 인풋창이 비면 최근 검색어가 나타나야 하는데 해결 전에는 역시나 최근 검색어가 나타나고 뒤늦게 도착한 자동 완성 리스트가 출력되는 오류가 있었다.
하지만 이제는 최근 검색어가 나오면 abort시키도록 했기 때문에 오른쪽 콘솔에 에러가 계속 출력되는 것이다.

주의

AbortController는 fetch 요청을 취소하는 것이다. 어쨌든 요청이 들어가는 것이기 때문에 어차피 필요없는 요청 취소하면 되지~ 이렇게 생각해서 서버에 요청을 하면 안된다고 한다. 그래서 디바운싱과 쓰로틀링이 있는 것이고 이 방법들을 적절히 잘 조합해 사용해야 한다.

fetch request의 처리를 취소하기
자바스크립트 ES6+: Fetch에서 요청을 취소하는 방법

좋은 웹페이지 즐겨찾기