예전에 과제전형 테스트에서 "비동기 요청 *초 전까지 응답이 오지 않는다면,
요청 취소 후 재전송"이라는 요구사항이 있었는데
그 이후(시원하게 말아먹고 난 이후) AbortController의 존재에 대해서 알게 되었다. 👍👍
먼저 간단하게 비동기 요청시 사용되는 fetch API의 특징을 간단하게 알아보고,
포스팅의 주 대상 AbortController를 공부해보자.
fetch : return Promise
fetch API의 결과는 Promise이다.
응답이 오기 전 까지 pending(대기) 상태를 유지하게 되며,
pending 상태가 끝나면 fullfilled 혹은 rejected로 바뀌게 된다.
fetch 자체만으로는 pending을 취소하는 방법이 따로 존재하지 않으며,
여기서 pending 상태는 "요청하고 응답을 기다리는중" 이므로, 어쩌면 응답을 무한정 기다려야 될 수도 있다.
AbortController?
Abort = (도중에) 중단시키다.
웹 요청 취소를 지원하는 객체이다.
new 키워드와 함께 호출하여 생성자로 AbortContoller를 생성하여, 비동기 작업시 이용할 수 있다.
알아야 할 프로퍼티는 2가지다. signal, abort!
AbortController.signal
signal은 읽기전용 프로퍼티로, fetch 요청 전송시 옵션으로 함께 넣어주게 된다.
fetch api에서 signal 옵션으로 넣어줌으로서, XHR 객체(요청)와 생성한 AbortController 인스턴스가 통신할 수 있게 된다.
AbortController.abort()
abort 메서드를 호출하면 요청 완료전에 취소할 수 있다.
abort 메서드가 signal 객체의 내부 상태를 바꿔준다.
요약하면 AbortController 인스턴스를 생성하고, signal로 신호,컨트롤러와 요청간 연결 후 abort 메서드로 취소할 수 있다.
사용 예시
app.get('/abort', (req, res) => {
const targetNumber = Math.random();
let randomNumber = Math.random();
let cntOfFailure = 0;
while (targetNumber !== randomNumber) {
if (cntOfFailure > 1e8 * 5) res.send('fail');
randomNumber = Math.random();
cntOfFailure++;
}
res.send('success');
});
간단하게 5억번의 기회 안에 랜덤 소수를 맞추면 "success" 응답을 주는 endpoint를 만들어보았다.
어차피 못맞출거다. 혹시 "success"를 받았다면 무척 운이 좋은 것이니, 당장 로또사러..
아무튼 해당 요청은 최소 1번에서 최대 5억번의 while문이 돌아가야 하기 때문에 "꽤 오래걸릴 예정이다."
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
</head>
<body>
<button onClick="request()">요청하기</button>
<button onClick="cancel()">요청 취소하기</button>
<p id="status"></p>
<script>
const status = document.querySelector('#status');
let abort = new AbortController();
async function request() {
try {
status.innerHTML = '요청중....';
const res = await fetch('--요청이 오래걸릴 수 있는 url--', {
signal: abort.signal,
});
status.innerHTML = `요청에 대한 응답이 도착하였습니다!!.`;
} catch (err) {
status.innerHTML = `에러 로그 : ${err}`;
}
}
function cancel() {
status.innerHTML = '요청 취소!';
abort.abort();
}
</script>
</body>
</html>
그리고 간단하게 html파일로 테스트 해보자.
- 요청하기 버튼을 누르면 "요청중..." 메시지를 보여준다.
- fetch option에 signal을 포함하여 비동기요청을 보낸다.
- 응답이 오면 "요청에 대한 응답이 도착하였습니다!!" 메시지를 보여준다.
- 응답이 오기 전에 요청 취소하기 버튼을 누르면, "요청 취소!" 메시지를 보여준다.
- 에러 발생시에는 "에러로그 : error message" 메시지를 보여준다.
요청이 잘 동작하는 모습이다.
abort.signal.addEventListener('abort', () => alert('취소'));
signal 객체에는 이벤트리스너도 달아줄 수 있다.
"abort" 이벤트 발생시, 콜백을 실행하게 되고, alert을 띄워주도록 동작시켜봤다.
마지막으로 너무 오래 기다리다가 결국 취소한 요청을 다시 전송하고 싶을 수 있다.
이 경우를 다뤄보자.
요청 취소 후 재요청하기.
아마 취소 후에 "요청하기"버튼을 다시 누르면 요청을 수행할 것 같다.
그런데 생각과는 다르게, 다시 요청이 전송되지 않는다.
AbortError: Failed to execute 'fetch' on 'Window': The user aborted a request.
AbortController.signal.aborted 값이 true이기 때문이다.
그럼 AbortController를 재생성해주면 되겠다.
async function request() {
try {
abort = new AbortController();
status.innerHTML = '요청중....';
...
}
}
간단하게 request 함수 실행시 객체를 만들어주자.
MDN 한국번역본에서는 Experimental이라고 해서, 영어로 바꿔보니 Experimental 문구는 사라졌다.
https://developer.mozilla.org/en-US/docs/Web/API/AbortController
IE 빼고 모두 지원하고 있다.
참고
https://developer.mozilla.org/en-US/docs/Web/API/AbortController
'Browser' 카테고리의 다른 글
[CORS] 다른 출처간 CORS 에러 해결과 웹팩 개발 서버의 Proxy 설정 (1) | 2021.11.15 |
---|---|
웹 크롤링과 웹 크롤러 봇 (0) | 2021.11.14 |