Http request 요청이 진행중일 때 페이지가 전환되면 오류가 발생할 수 있다.예를 들면, 이벤트가 발생했는데, response보다 빠르게 페이지가 전환되어 더이상 화면에 없는 component의 요소를 업데이트 해야 하는 상황이 발생할 수 있다.
이런 경우에는 실제로 진행 중인 HTTP request를 취소할 수 있어야 하는데 최신 브라우저에서 제공하는 AbortController라는 생성자 함수가 존재한다.
import {useState,useEffect,useCallback,useRef} from 'react'
export const useHttp = () => {
const [isLoading,setIsLoading] = useState(false);
const [error,setError] = useState();
//AbortController
// 1. 현재 진행중인 HttpRequest를 담을 수 있는 배열을 생성하고 useRef에 담아준다.
// 부수작업이고, UI를 업데이트 하기 위한 작업이 아니기에 useState보다는 useRef가 적합하다.
// useRef를 통해 화면에는 보이지 않게 useHttp hook을 사용하는 compoenent가 랜더링 될 때마다 다시 실행된다.
const activeHttpRequests = useRef([]);
const sendRequest = useCallback( async(url, method= 'GET',body,headers={}) => {
setIsLoading(true)
// 2. Request를 보내기 이전에 AbortController 생성자 함수 생성
// AbortController는 현재 활성화 된 HTTP requests를 보여준다.
const httpAbortController = new AbortController();
// 3. sendRequest가 실행될 때마다 activeHttpRequests에 AbortController를 push한다.
activeHttpRequests.current.push(httpAbortController);
try{
const response = await fetch(url,{
method,
body,
headers,
// 4.생성된 AbortController를 가리킴으로서 해당 http Request를 취소할 수 있다.
signal:httpAbortController.signal
});
const responseData = await response.json()
// 5.해당 Http request에서 응답을 받게 되면 AbortController 배열에서 해당 AbortController를 삭제한다
if(!response.ok){
setError(responseData.message)
}
setIsLoading(false)
return responseData;
}catch(err){
setError(err.message)
setIsLoading(false)
throw err;
}
},[])
const clearError = () => {
setError(null)
}
useEffect(()=>{
// 6. clean up function을 통해 다음 sendRequest가 실행 되기 전에,
// 정확히는 useHttp hook이 unmount 될 때 ref에 저장된 AbortController를 삭제한다.
return () =>
{
activeHttpRequests.current.forEach(abortController => abortController.abort())
};
},[])
return {
error,
isLoading,
sendRequest,
clearError
}
}
1.abort()
abort()가 호출되면, fetch() promise는 AbortError으로 명명된 DOMException과 함께 reject된다.
2. AbortController.signal
AbortController 인터페이스의 signal은 읽기 전용 프로퍼티로서
DOM 요청과 통신하거나 취소하는데 사용하는 AbortSignal 객체 인터페이스를 반환한다.