[ProPro] axios 비동기처리 이슈

jiseong·2022년 5월 13일
0

T I Learned

목록 보기
244/291

VanillaJS로 만든 ProPro 서비스에서 고려하지 못한 문제점이 생겼다.
서비스에는 여러 페이지가 존재하고 특정 페이지에서는 서버로부터 필요한 데이터를 받아오는 과정이 있는데 이 시점에 다른 페이지로 이동하면 이후에 비동기처리 동작이 완료되면서 이전의 페이지를 화면에 렌더링하고 있었다.

문제의 원인

팀원과 함께 만든 Core Component의 setState() 메서드를 사용하면 해당 컴포넌트의 render()메서드를 호출하여 화면을 다시 렌더링하게 된다.

// coreComponent.js
  setState(nextState) {
    this.state = nextState;
    this.render();
  }

그런데 다음과 같이 특정 페이지에서 비동기적으로 데이터를 서버로부터 받아오는 과정에서 다른 페이지로 이동하여도 데이터를 정상적으로 받아오게 되면 this.setState() 메서드를 호출하여 해당 컴포넌트를 렌더링하게 되는 것이였다.

  async mounted() {
    try {
      const {
        data: { data: { profile } },
      } = await axiosInstance.get('/users');

      this.setState({
        ...this.state,
        ...profile,
      });
    } catch (e) {
      new Toast({ content: '프로필 정보 불러오기 실패', type: 'fail' });
    }
  }

문제의 해결방식

AbortController를 활용하여 페이지 변경시 요청중인 비동기 작업을 중단하는 방식을 고려해봤다.

AbortController는 하나 이상의 웹 요청을 조작할 수 있는 컨트롤러 객체를 제공하여 요청 중인 request를 중단할 수 있는 방법을 제공해주는 object interface이다.

AbortController Object가 가지는 property와 Method로는

  • AbortController.signal

  • AbortController.abort()

가 존재하며 MDN에서 제공하는 사용예시는 다음과 같다.

const controller = new AbortController(); // (1)

const signal = controller.signal; // (2)

fetch('https://example.com', { signal }) // (2)
  .then(function(response) {
  console.log('Download complete', response);
})
  .catch(function(e) {
  console.log('Download error: ' + e.message);
});


controller.abort(); // (3)

(1) AbortController를 사용하여 새로운 인스턴스를 만든 후에 (2) 해당 인스턴스의 property인 signal를 관리하고자하는 비동기 요청의 signal 옵션에 할당한다.

그런다음 상황에 따라서 해당 요청을 중단하기 위해서 (3) 해당 인스턴스의 Method인 abort() 메서드를 호출해주면 된다.

이제 이 AbortController를 사용하면 페이지가 이동할때마다 요청중인 것들을 중단하고자 한다면 가장 적절한 위치가 Routing을 하는 Router라고 생각되어서 Router.js에 코드를 작성하였다.

// Router/Router.js

route() {
   // 생략..
    if (webRequestController) {
       // 기존의 비동기 요청 취소
      webRequestController.getController().abort();
       // 다음 비동기 요청을 추적할 수 있도록 새로운 AbortController 인스턴스 생성
      webRequestController.resetController();
    }
   // 생략...
 }

여기서 사용되는 webRequestController는 다음과 같이 AbortController를 관리할 수 있는 컨트롤 역할의 클래스로 작성하였고 기존에 비동기 요청이 있다면 취소하고 새로운 AbortController 객체를 생성하여 다음 페이지의 비동기 요청을 추적할 수 있도록 리셋하는 방식으로 작성하였다.

class WebRequestController {
  constructor() {
    this._abortController = new AbortController();
  }

  resetController() {
    this._abortController = new AbortController();
  }

  getController() {
    return this._abortController;
  }
}

export default new WebRequestController();

그러면 이제 각 페이지에서 비동기 통신을 하는 부분에서 signal 옵션을 활성화하여 추적만 해주면 페이지 이동이 되었을 때 abort() 메서드를 호출하여 기존의 비동기 요청을 중단시킬 수 있다.

async mounted() {
    try {
      const {
        data: { data: { profile } },
      } = await axiosInstance.get('/users', {
        withCredentials: true,
        signal: webRequestController.getController()?.signal, // 추적할 수  있는 signal를 주입
      });

      this.setState({
        ...this.state,
        ...profile,
      });
    } catch (error) {
      // 다만, 비동기 요청을 중단하게 되면 error(Message: Canceled)로 빠지기 때문에 
	  // error 핸들링으로 취소일때는 아무런 처리를 하지 않도록 추가적인 처리가 필요하다.
      if (isCanceledRequest(error)) return;
      
      new Toast({ content: '프로필 정보 불러오기 실패', type: 'fail' });
    }
  }
  
// export const isCanceledRequest = error => axios.isCancel(error);

처음에는 현재 pathName에 따라 this.setState()메서드를 호출여부를 결정하는 방식이 떠올랐는데 한 팀원분이 axios도 취소할 수 있는 방식이 존재한다고 알려주셔서 AbortController를 활용해보게 되었고 해당 방식을 사용하여 비동기 요청이 진행되는 시점에서 페이지를 이동하여도 기존의 비동기 요청을 중단함으로서 이전 페이지로 돌아가는 현상을 해결할 수 있었다.

관련 이슈


Reference

0개의 댓글