대부분의 프론트엔드 개발에서는 서버와 통신하기 위해 비동기 웹 요청을 사용합니다. 하지만 비동기 웹 요청은 응답 시간과 결과 값을 예측하기 어려우며 사이드 이펙트를 발생시키는 제어하기 힘든 요소
중 하나입니다.
AbortController
객체를 사용하면 이러한 비동기 웹 요청을 부분적으로 제어할 수 있습니다. AbortController는 브라우저 Web API로 비동기 웹 요청을 중단할 수 있는 기능을 제공합니다.
응답 시간이 오래 걸리거나 응답이 더 이상 필요하지 않은 경우 요청을 중단할 수 있습니다. 이렇게 요청을 제어하여 불필요한 서버 요청을 줄여 성능을 개선할 수 있으며, 사용자에게 더 나은 사용자 경험을 제공할 수 있습니다.
이번 글에서는 AbortController의 개념과 활용 방법을 알아보겠습니다.
AbortController API의 기본적인 사용방법은 다음과 같습니다.
const controller = new AbortController();
// controller 객체는 signal 프러퍼티와 abort() 메서드를 가집니다
controller.signal;
controller.abort();
signal
프러퍼티를 연결합니다. fetch (URL, {
signal: controller.signal
})
abort()
메서드로 요청을 중단합니다. controller.abort()
abort() 메서드에는 중단이유
를 인자로 넘겨줄 수 있습니다. 여러 케이스에 따라 요청을 중단할 수 있습니다.
setTimeout(()=>{
controller.abort("응답시간이 너무 깁니다")
},1000)
cancelButton.onclick(()=>{
controller.abort("사용자가 취소버튼을 눌렀습니다")
})
이러한 중단이유는 abort()된 후 signal.reason
프러퍼티로 가져올 수 있습니다. 중단이유에 따라 핸들러를 작성할 수 있습니다.
controller.signal.reason
// "사용자가 취소버튼을 눌렀습니다" 또는 "응답시간이 너무 깁니다"
abort() 메서드가 실행되면 요청은 중단되고 fetch API는 AbortError
에러를 던집니다.
const controller = new AbortController();
const fetchData = async () => {
const response = await fetch("https://httpbin.org/delay/10", {
signal: controller.signal
})
}
fetchData()
.then()
.catch(error=>{
if(error.name === "AbortError"){
// abort 핸들러
abortHandler();
}
})
// 1초가 지나면 응답여부에 상관없이 요청을 중단합니다.
setTimeout(()=>{
controller.abort();
},1000);
3초가 넘어가도 응답이 오지않는다면 요청을 중단하고 사용자에게 알리는 코드를 작성해봅시다.
controller.abort()
메서드를 사용할 때 주의해야할 점은 응답 존재 여부에 상관없이 요청을 중단한다는 점입니다. 따라서 응답의 존재 여부를 확인 후 abort() 시켜야합니다.
signal.addEventListener("abort")
를 사용하면 요청이 중단되었을 때 수행할 이벤트 핸들러를 정의할 수 있습니다.
만약 응답의 존재여부를 확인하지 않고 controller.abort()를 호출한다면 응답이 정상적으로 왔음에도 항상 "abort" 이벤트가 실행되기 때문에 응답의 존재여부를 확인하는 것은 중요합니다.
const controller = new AbortController();
let response;
const fetchData = async () => {
response = await fetch("https://httpbin.org/delay/10", {
signal: controller.signal
});
};
fetchData()
.then()
.catch(e=>{
if(e.name === "AbortError"){
// abort handler
}
})
controller.signal.addEventListener("abort", () => {
console.log("aborted");
});
setTimeout(() => {
// 응답이 존재하지 않는 경우만 요청을 중단합니다.
if (!response) {
controller.abort("요청이 너무 느립니다");
}
}, 3000);
AbortSignal.timeout 정적메서드를 사용하면 훨씬 짧고 간단하게 기능을 구현할 수 있습니다.
하지만 Chrome과 Safari에서 버그가 있는 기능이기 때문에 버그가 픽스되었을 때를 대비해서 실제 사용은 조심해야합니다. (관련 버그1)
AbortSignal.timeout() 정적메서드는 지정한 시간만큼 지난 후 abort()하는 AbortSignal 객체를 반환합니다. new AbortController()
와 setTimeout()
을 사용하지않고 코드를 단순화 할 수 있습니다.
const fetchData = async () => {
response = await fetch("https://httpbin.org/delay/10", {
// 1초뒤 자동으로 요청을 abort() 합니다
signal: AbortSignal.timeout(1000)
});
};
fetchData()
.then()
.catch(e=>{
if(e.name === "AbortError"){
console.log("abort");
}
})
비동기 웹 요청뿐만 아니라 AbortController로 이벤트 리스너를 손 쉽게 제거할 수 있습니다.
일반적으로 이벤트 리스너를 제거하기 위해서는 이벤트를 등록할 때 함수의 참조값을 알고 있어야합니다. 참조값이 불일치하는 경우 이벤트 리스너를 제거할 수 없습니다.
// BAD
window.addEventLitener("click", ()=> {});
window.removeEventListener("click", ()=> {}); // 이벤트리스너를 제거할 수 없습니다.
// GOOD
const clickHandler = () => {};
window.addEventLitener("click", clickHandler);
window.removeEventListener("click", clickHandler); // 동일한 함수 참조값을 가져야합니다.
AbortController과 AbortSignal을 사용하면 핸들러의 참조값을 알지못해도 이벤트 핸들러를 제거할 수 있습니다.
const controller = new AbortController();
window.addEventListener("click", () => {
console.log("click");
},{
signal: controller.signal
});
// "click" 이벤트 핸들러가 제거됩니다.
controller.abort();
axios는 원래 cancelToken
기반으로 요청을 취소할 수 있었습니다. 하지만 cancelToken은 중단된 TC39 제안 proposal-cancelable-promises 기반이기 때문에 현재는 deprecated되었습니다.
axios는 v.0.22.0
부터 abortController를 지원합니다. 사용하는 방법은 동일합니다.
const controller = new AbortController();
axios.get('/foo/bar', {
signal: controller.signal
})
controller.abort()
node.js에서도 v15.0.0
부터 AbortController를 지원합니다.
이번 글에서는 AbortController를 이용해 비동기 웹 요청을 중단하는 방법에 대해 알아보았습니다.
AbortController를 사용하면, 이미 요청이 발생한 상황에서도 중간에 해당 요청을 취소할 수 있어서, 불필요한 리소스를 낭비하지 않고 빠르게 다른 작업으로 전환할 수 있습니다. 또한, 이를 이용하여 페이지 전환 등을 수행할 때, 중복된 요청을 방지할 수 있습니다
감사합니다.
너무너무 좋은데요!!