본문 바로가기
IT/개발

[Js, Vue] AbortController + onMounted + onUnMounted

by aloveu 2024. 4. 22.
반응형

안녕하세요! aloveu입니다.

겪었던 오류 이슈들을 정리하다 보니 문득문득 생각나는 것들이 있네요.

오늘은 Vue의 생명주기인 onMounted, onUnmounted에 대해 설명을 드리고 Js에서 Fetch요청을 중단시킬 수 있는 AbortController에 대해서도 같이 설명을 드리겠습니다.

먼저 관련 없어 보이는 이 두개를 왜 같이 묶어서 포스팅을 하는지 배경부터 설명을 드리겠습니다.


배경

Vuejs로 만들어진 프로젝트에서 빠르게 페이지를 이동을 하면 이전 페이지 onMounted에서 호출했던 API들이 취소가 안되고 다른 페이지에 이동한 이후에도 호출되고 있던 것이었어요.

결론은 onMounted도 하나의 function으로 이미 호출된 시점에서 함수 내부 로직을 끝까지 완수했던 것뿐이었어요. 자기 자신의 소임을 다한 거죠.

근데 저는 알아서 취소를 왜 안 해주냐면서 멍청하다고 속으로 요ㄱ....

 

네 어쨌든 onUnmounted 때 아직 pending 상태에 있는 호출들을 일제히 취소시키는 게 필요했습니다. 그때 필요한 게 바로 AbortController입니다.

 

onMounted

Vue의 생명주기 중에 하나로 컴포넌트가 마운트 된 이후에 호출될 콜백들을 등록할 수 있습니다.

import { onMounted } from 'vue';

onMounted(async () =>{
  console.log('Mounted');
});

 

onUnmounted

마찬가지로 Vue의 생명주기중에 하나이고 컴포넌트가 해제된 이 후에 호출됩니다. 

import { onUnmounted } from 'vue';

onUnmounted(async () =>{
  console.log('Unmounted');
});

 

이 포스팅에서는 이 두 개의 생명주기가 중요하진 않아서 자세히 설명하지는 않고 간단히 이렇게 사용한다 정도만 알고 넘어가겠습니다. 

 

AbortController

이 인터페이스는 하나이상의 웹 요청을 취소할 수 있게 도와줍니다. 문서를 찾아 보면 AbortController는 범용적으로 설계가 되어있어서 어떤 비동기 API에도 사용이 가능합니다. 

그래서 Fetch, Axios등에도 적용하기 쉽죠. 그냥 스크립트일 뿐입니다.

const abortController = new AbortController();

fetch( 'http://example.com', {
  signal: abortController.signal
} ).catch( ( { error } ) => {
  console.error( error );
} );

abortController.abort();

 

사용법은 간단합니다. abortController에 대한 새로운 인스턴스를 만들고 fetch함수의 option으로 만든 인스턴스의 signal을 함께 보냅니다. 

그러고 나서 취소하고 싶을 때 abortController.abort()를 호출해 주면 취소가 되죠.

 

주의할 점은 이렇게 취소가 되면 catch로 넘어가게 되는데 이때 취소에 대한 에러 핸들링은 따로 해 주셔야 합니다.

가령 api 호출 실패 때마다 Toast 알럿으로 에러 났다고 노티를 보내주는 상황들 말이죠. 내가 강제로 취소한 상황에서는 보여주고 싶지 않을 테니깐요.

 

Vuejs에서 사용법

Vuejs에서는 api 서비스호출 할 때 같이 signal을 넘기면 되고 onUnmount 할 때 abort()를 호출해 주시면 됩니다.

// api service
const apiService = {
  ...
  callApi: (data: RequestDTO, signal?: AbortSignal){
    axios.post<AxiosResponse, void>(`/apiUrl`, data, { signal }); 
  }
}

// component
import { onMounted, onUnmounted } from 'vue';

const abortController = new AbortController();

onMounted(() => {
  test();
});

async function test(){
  try{
    await apiService.callApi({data}, abortController.signal);
  } catch(e){
    console.error(e);
  }
}

onUnmounted(async () => {
  abortController.abort();
});

 

보통 api 쪽 서비스가 따로 빠져있을 테니 그걸 기반으로 설명을 드리자면 signal이라는 인자를 하나 더 api 쪽으로 넘겨서 http호출 라이브러리 옵션으로 전달해 주면 됩니다. 

참 쉽죠?

 

아 귀찮은데 이렇게 까지 해야 돼?라고 하시는 분들을 위해 제가 겪은 에러에 대한 상황을 같이 적어드리겠습니다.

 

Error Case

배경에서 설명했다시피 onMounted가 실행되고 있는 도중에 페이지를 이동을 했을 때 발생했습니다. 

이동을 한 뒤에 호출을 더 하는 건 리소스 낭비일 뿐만 아니라 예기치 않은 오류를 발생시켰어요.

 

예를 들어볼게요.

import { onMounted } from 'vue';

onMounted(async () =>{
  await callApi1();
  await callApi2();
  await callApi3();
});

 

극단적인 예를 들었지만 이런 상황에서 페이지를 빠르게 이동을 했다고 합시다.

근데 callApi1,2에서 시간을 많이 잡아먹어서 아직 callApi3번이 호출이 되지 않은 상황이에요.

callApi3번은 store에 있는 공통 state값을 수정하는 로직이 들어가져 있습니다.

그리고 이동을 한 컴포넌트에서도 onMounted가 호출되면서 store에 있는 state값을 수정을 해요.

 

어떤 오류가 발생하게 될까요??

네 이동을 한 컴포넌트에서 수정한 state값이 이전 페이지에서 호출된 함수(callApi3)로 인해 덮어 씌워지게 됩니다.

이게 매번 발생하면 바로 알아차리고 수정을 했을 텐데 가끔 정말 간헐적으로 발생했어요.

코드가 저렇게 극단적이지 않았기도 했고, api응답속도도 빨랐거든요.

또 유저가 빠르게 페이지를 이동하지 않았으니 발생을 하지 않았던 겁니다. 

 

근데 api응답이 느려진 타이밍에 유저가 메뉴를 잘못 클릭했는지 빠르게 다른 페이지로 이동을 하면서 발생을 했어요!!!

 

무려 라이브에서요. 자주 발생하지 않아서 왜 state 값이 이전 페이지의 값을 가지고 있는지 디버깅도 어려웠지요. 

그래서 미리 이런 방어코드들을 넣어 두면 편안해질 겁니다.

 

결론

방어코드들을 항상 염두해두고 코딩하자

 

이게 React 같은 라이브러리나 Angular 프레임워크 같은 곳에서도 똑같은 현상이 있을 거예요.

아니면 그냥 바닐라 JS로 된 프로젝트라도요.

어느 상황에서든 요청 중인 API를 취소하고 싶으실 땐 AbortController를 기억하면 됩니다.

사실 기억하지 않더라도 '아 뭔지는 모르겠는데 그런 기능이 있었지?!'정도만 생각해도 구글신이 도와줄 것이기 때문에 이 포스팅 한번 보신 것만으로도 충분할 거라 생각합니다.

 

그럼 읽어주셔서 감사드리고 즐코딩하세요!

^___^

 

 

참고

https://ko.vuejs.org/api/composition-api-lifecycle

https://developer.mozilla.org/ko/docs/Web/API/AbortController/abort

 

반응형

'IT > 개발' 카테고리의 다른 글

[CSS] 버튼을 누를 때 폭죽 터지는 animation을 넣어보기  (136) 2024.04.29
[NPM] Axios Error  (38) 2024.04.23
[Vue] Router  (84) 2024.04.21
[Angular] HttpClient  (75) 2024.04.16
[Vite] vite로 번들링 도구를 바꾸기  (81) 2024.04.14