우리가 알고 있듯이, fetch는 프로미스를 반환합니다. 보통 자바스크립트에는 프로미스를 “중단하는” 개념은 없습니다. 그렇다면 진행 중인 fetch를 어떻게 취소할 수 있을까요? E.g) 만약 사이트에서 사용자 액션이 fetch가 더 이상 필요하지 않다고 판단되는 경우
이러한 경우를 위해 만들어진 특별한 객체가 AbortController 입니다. fetch를 중단할 때 쓸 수 있을 뿐 아니라, 비동기 작업에도 쓸 수 있습니다.
사용 방법은 간단합니다.
컨트롤러를 생성합니다.
let controller = new AbortController();컨트롤러는 아주 단순한 객체입니다.
abort()라는 한가지 메소드를 가지고 있습니다.- 그리고
signal라는 한 가지 프로퍼티를 가지며, 이 프로퍼티로 이벤트 리스너를 설정할 수 있습니다.
abort() 가 호출될 때:
controller.signal이"abort"이벤트를 발생시킵니다.controller.signal.aborted프로퍼티가true가 됩니다.
일반적으로 이 과정에는 2가지 그룹이 있습니다.
- 취소 작업을 실행하는 그룹은
controller.signal에 리스너를 설정합니다. - 취소되는 그룹은 필요시에
controller.abort()를 호출합니다.
전체 예시는 다음과 같습니다 (fetch 적용 전).
let controller = new AbortController();
let signal = controller.signal;
// 취소 작업을 실행하는 그룹이
// "signal" 오브젝트를 받고
// controller.abort() 가 호출될 때 리스너가 실행되도록 설정합니다.
signal.addEventListener('abort', () => alert("abort!"));
// 취소되는 다른 그룹 (추후에):
controller.abort(); // abort!
// 이벤트가 발생하고 signal.aborted는 true가 됨
alert(signal.aborted); // true여기서 확인할 수 있듯이, AbortController 는 abort()가 호출될 때 abort 를 전달하는 수단입니다.
AbortController 객체를 전혀 쓰지 않고, 코드를 따로 작성해서 동일한 이벤트 리스너를 구현할 수도 있습니다.
하지만 중요한 점은, fetch가 AbortController 객체와 어떻게 작동하는지 알고 있으며, 합칠 수 있다는 점입니다.
fetch를 취소하려면, fetch 옵션으로 AbortController의 프로퍼티인 signal을 전달합니다.
let controller = new AbortController();
fetch(url, {
signal: controller.signal
});fetch 메소드는 AbortController와 어떻게 작동하는지 알고 있습니다. signal에서 abort 이벤트를 듣습니다.
이제 중단하려면 controller.abort()를 호출합니다.
controller.abort();다 끝났습니다. fetch 는 signal에서 이벤트를 받고, fetch를 중단합니다.
fetch가 중단되면, 프로미스는 AbortError에러로 reject됩니다. 그래서 이를 처리해주어야 합니다. e.g) try..catch.
1초 뒤에 fetch 가 중단되는 전체 예시는 다음과 같습니다.
// 1초 뒤에 중단
let controller = new AbortController();
setTimeout(() => controller.abort(), 1000);
try {
let response = await fetch('/article/fetch-abort/demo/hang', {
signal: controller.signal
});
} catch(err) {
if (err.name == 'AbortError') { // abort() 처리
alert("Aborted!");
} else {
throw err;
}
}AbortController 는 확장이 가능하기 때문에 한 번에 여러 개의 요청을 취소할 수 있습니다.
다음 코드는 많은 urls을 병렬 작업으로 요청하고, 한꺼번에 중단하기 위해 하나의 컨트롤러를 사용하고 있습니다.
let urls = [...]; // 병렬 작업으로 요청할 url 목록
let controller = new AbortController();
// fetch 프로미스 배열
let fetchJobs = urls.map(url => fetch(url, {
signal: controller.signal
}));
let results = await Promise.all(fetchJobs);
// 만일 controller.abort() 가 다른 곳에서 호출되면,
// 모든 fetch를 중단합니다.만일 fetch 가 아닌 다른 비동기 작업이 있다면, 하나의 AbortController 를 사용해서 fetch와 함께 모두 중단할 수 있습니다.
작업시에 해당 abort 이벤트를 듣기만 하면 됩니다.
let urls = [...];
let controller = new AbortController();
let ourJob = new Promise((resolve, reject) => { // 다른 작업
...
controller.signal.addEventListener('abort', reject);
});
let fetchJobs = urls.map(url => fetch(url, { // fetches
signal: controller.signal
}));
// fetch와 다른 작업을 병렬 작업으로 기다림
let results = await Promise.all([...fetchJobs, ourJob]);
// 만일 controller.abort() 가 다른 곳에서 호출되면,
// 모든 fetch와 작업이 중단됩니다.AbortController는abort()메소드가 호출될 때signal프로퍼티에abort이벤트를 생성하는(또한signal.aborted를true바꾸는) 단순한 객체입니다.fetch와 합칠 수 있습니다. 옵션으로signal프로퍼티를 전달하면fetch는 이를 듣고,fetch를 중단할 수 있게 됩니다.- 코드에
AbortController를 쓸 수 있습니다. "abort()호출” -> "abort이벤트 듣기" 상호작용은 단순하며 범용적입니다.fetch없이도 쓸 수 있습니다.