woolta

자바스크립트에서 비동기 네트워크 호출 취소하기 (AbortController, axios cancellation)

wooltaUserImgjavaScript | 2020-11-22

비동기 통신 취소 하기

프론트 영역에서 비동기 통신은 보통 성공 or 실패를 반환하게 됩니다. 물론 비즈니스 로직을 통해서 네트워크를 무시하고 취소상태로 할수는 있으나 실제 네트워크 요청은 그대로 진행되게 됩니다. 하지만 AbortController 를 사용하면 네트워크 요청을 취소할 수 있습니다.

AbortController 란

AbortController하나 이상의 웹 요청을 취소할수 있게 해주는 인터페이스로 요청중인 네트워크 호출 자체를 취소할 수 있게 도와줍니다. 이러한 비동기 작업의 취소를 표준화 시키기 위해서 TC39 에서 proposal-cancellation로 논의하였으나 결국 통과하지 못하고 이후 WHATWG 에 의하여 Dom 인터페이스에만 도입하였습니다. 즉. 해당 스펙은 node.js 에서는 별도의 폴리필이 없다면 사용할 수 없습니다.

AbortController 구성 요소

AbortController 는 아래와 같이 각각1개의 프로퍼티와 메소드를 가지고 있습니다.

프로퍼티 - signal

DOM 요청과 통신 혹은 취소시 사용하는 abortSiganl 객체 인터페이스를 반환합니다. (읽기 전용) 해당 signal 을 네트워크 요청시 같이 담아서 취소할수 있는 역할을 담당합니다.

메소드 - abort

비동기 요청이 완료되기 전 abort 메소드를 통하여 해당 비동기 요청을 취소할 수 있습니다.

AbortController 작성 예시 코드

서버 코드 작성

네크워크 요청을 실패하는 테스트를 위해 10초간 기다린 후 응답하는 테스트용 api 서버를 작성해 보도록 하겠습니다. 본 예제는 express 기반이지만 딜레이가 걸리는 api가 있다면 해당 api 로 진행하셔도 무방합니다. :)

const express = require('express') const app = express() const port = 4000 // 4000 포트 실행 app.get('/abort', (req, res) => { setTimeout(() => res.send('abort call !!!'), 10000); // 10초 후 응답하기 }); app.listen(port, () => { console.log(`Example app listening at http://localhost:${port}`) });

위의 서버를 기동시켜 10초간의 네트워크 요청이 필요한 api 를 만들었습니다. 이제는 해당 네트워크를 호출하고 취소하는 클라이언트쪽 코드를 작성해 보도록 하겠습니다. ( 예시 코드는 react를 통해 작성하였습니다. )

클라이언트 소스 코드

import React, { useState } from 'react'; const abortController = new AbortController(); // AbortController 인스턴스 생성 const signal = abortController.signal; // 취소 요청을 위한signal 할당 function AbortControllerTestComponent() { const [loading, setLoading] = useState(false); // 비동기 통신 로딩 체크 용도 const onSendClick = async () => { try { // 로딩 시작 setLoading(true); const response = await fetch('http://localhost:4000/abort', { signal }); // 취소 siganl 보내주기 const data = await JSON.stringify(response); console.log(data); } catch (e) { console.log(e); } finally { // 비동기 호출 이후 로딩 종료 setLoading(false); } }; const onCancelClick = () => { // 취소 요청 abortController.abort(); }; return ( <div> {loading ? <p>로딩중 입니다.</p> : <button onClick={onSendClick}>전송버튼</button>} <button onClick={onCancelClick}>취소버튼</button> </div> ); }

위의 코드의 큰 코드 흐름은 다음과 같습니다.

  1. AbortController의 인스턴스 생성
  2. 생성한 AbortController 인스턴스 의 signal 프로퍼티를 네트워크 호출 함수인 fetch에 추가
  3. AbortController 인스턴스의 abort 메소드를 호출하여 해당 비동기 요청 취소
  4. 취소 요청이 오면 해당 Promise 를 바로 reject 후 AbortError 에러 반환

이렇게 abort 메소드를 호출하여 취소하게 되면 해당 promise 는 바로 reject 되고 실제 해당 네트워크 호출도 바로 취소되는것을 확인 하실수 있습니다. 이를 try-catch 를 통해 잡아보면 AbortError의 이름으로 DOMException 에러를 반환하는것을 확인하실 수 있습니다.

위의 코드 실행결과는 다음과 같습니다.

코드 실행 결과

https://image.woolta.com/abortContoller.gif 취소 버튼 클릭시 우측의 네트워크 상태를 보면 성공적으로 취소한 것을 확인하실 수 있습니다. :)

폴리필

또한 AbortController 에 대한 폴리필도 https://www.npmjs.com/package/abortcontroller-polyfill 를 받아서 사용 가능합니다.

Promise 취소하기

비동기 통신이 아닌 promise 상태의 취소를 하고 싶다면 signal 의 abort 이벤트 리스너를 통해 promise를 취소할 수 있습니다.

const abortController = new AbortController();
const signal = abortController.signal;

function promiseCancel(abortSignal: any) {
  return new Promise((resolve, reject) => {
    // 10초 뒤 resolve 반환
    setTimeout(() => resolve('success'), 10000); 
   
    //  signal 프로퍼티의 abort 이벤트리스너를 구현
    abortSignal.addEventListener('abort', () => {
      const error = new DOMException('Calculation aborted by the user', 'AbortError');
      reject(error); // 8
    });
  });
}

// promise 호출시 signal 넣어주어 호출하기
promiseCancel(signal);
// promise 취소하기
abortController.abort(); 

각 promise 마다 해당 이벤트 리스너를 구현해주어야하는 불편함이 있지만 promise 작업에 대해서도 취소를 진행할 수 있습니다. :)

axios 에서 취소 요청 하기

axios 에서는 AbortController 를 지원하지는 않고 이와 같은 proposal-cancellation 제안을 기반으로 axios 에서 지원하는 취소 토큰(cancel token) 을 통해 비동기 호출을 취소할 수 있습니다.

-- axios 취소 예시 코드

const CancelToken = axios.CancelToken; // 취소 토큰 생성 const source = CancelToken.source(); try { const res = await axios.get('/localhost:4000/abort', { cancelToken: source.token }); } catch (e) { if (axios.isCancel(e)) { // 비동기 요청 취소해서 난 오류 } else { // 기타 다른 오류 } } // 비동기 요청 취소 ( 안의 매개변수는 에러메세지 정의) source.cancel('네트워크 호출을 취소하였습니다..');

위의 axios 를 통한 취소 예시를 보면 위의 AbortContoller 를 통한 취소 방식과 거의 동일한 모습임을 확인하실 수 있습니다.

참조