javaScript | 2020-11-22프론트 영역에서 비동기 통신은 보통 성공 or 실패를 반환하게 됩니다. 물론 비즈니스 로직을 통해서 네트워크를 무시하고 취소상태로 할수는 있으나 실제 네트워크 요청은 그대로 진행되게 됩니다. 하지만 AbortController 를 사용하면 네트워크 요청을 취소할 수 있습니다.
AbortController 는 하나 이상의 웹 요청을 취소할수 있게 해주는 인터페이스로 요청중인 네트워크 호출 자체를 취소할 수 있게 도와줍니다.
이러한 비동기 작업의 취소를 표준화 시키기 위해서 TC39 에서 proposal-cancellation로 논의하였으나 결국 통과하지 못하고 이후 WHATWG 에 의하여 Dom 인터페이스에만 도입하였습니다. 즉. 해당 스펙은 node.js 에서는 별도의 폴리필이 없다면 사용할 수 없습니다.
AbortController 는 아래와 같이 각각1개의 프로퍼티와 메소드를 가지고 있습니다.
DOM 요청과 통신 혹은 취소시 사용하는 abortSiganl 객체 인터페이스를 반환합니다. (읽기 전용) 해당 signal 을 네트워크 요청시 같이 담아서 취소할수 있는 역할을 담당합니다.
비동기 요청이 완료되기 전 abort 메소드를 통하여 해당 비동기 요청을 취소할 수 있습니다.
네크워크 요청을 실패하는 테스트를 위해 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> ); }
위의 코드의 큰 코드 흐름은 다음과 같습니다.
이렇게 abort 메소드를 호출하여 취소하게 되면 해당 promise 는 바로 reject 되고 실제 해당 네트워크 호출도 바로 취소되는것을 확인 하실수 있습니다. 이를 try-catch 를 통해 잡아보면 AbortError의 이름으로 DOMException 에러를 반환하는것을 확인하실 수 있습니다.
위의 코드 실행결과는 다음과 같습니다.
취소 버튼 클릭시 우측의 네트워크 상태를 보면 성공적으로 취소한 것을 확인하실 수 있습니다. :)
또한 AbortController 에 대한 폴리필도 https://www.npmjs.com/package/abortcontroller-polyfill 를 받아서 사용 가능합니다.
비동기 통신이 아닌 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 에서는 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 를 통한 취소 방식과 거의 동일한 모습임을 확인하실 수 있습니다.