Promise 취소하기 + Redux-Saga

Slowly But Surely·2023년 10월 20일

처음에는 axios.CancelToken로 구현을 했다가, Axios에 찾아보니 deprecated라고 해서 AbortController로 새로 구현했다.

상황

  • 서버 응답이 느리다고 가정
  • 서버에서 아직 응답이 오기 전으로, Promise는 pending state
  • 클라이언트에서는 이전 Promise가 fulfilled 되기까지 기다리지 않고 계속해서 새로운 요청을 보내서 요청이 쌓임

문제점

  • 실제로 사용할 응답은 가장 마지막에 보낸 요청에 대한 응답
  • 사용하지도 않을 요청을 전부 다 보내면 서버에 부담
  • 요청 보낸 순서대로 응답이 온다는 보장 없음
  • 실제 필요하지 않은 응답이 UI에 반영되는 등 계속 변화하기 때문에 사용자 경험에 좋지 않음

해결 및 구현

API 요청에는 axiosredux-saga, 상태관리는 reduxredux-toolkit 사용

// 현재 요청이 아니라 이전 요청을 취소해야 하기 때문에 closure로 fetchData 함수 scope 밖에 선언
import axios from "axios";
import { call, put, takeLatest } from "@redux-saga/core/effects";
import { startLoading, putDataToStore } from "/redux/slices";

let ctrller;

function* fetchSaga() {
  if (ctrller) {
    ctrller.abort(); // 이전 요청을 취소. 따라서 최초에는 abort할 것이 없음.
  }

  // Create a new AbortController
  ctrller = new AbortController(); // 새로운 요청을 위한 AbortController 생성

  try {
    yield put(startLoading()); // 로딩 시작
    const res = yield call(axios.get("some-url.com", {
      signal: ctrller.signal
    });
    // status !== 200인 경우 및 !data.resource인 경우 핸들링 생략
    yield put(putDataToStore(res.data.resource));                           
  } catch (error) {
    if (error.name === 'AbortError') {
      console.log('API request was aborted');
    } else {
      console.log(error);
    }
  } finally {
    yield put(startLoading());
  }
};


export function* watchDataSaga() {
  yield takeLatest(fetchStart, fetchDataSaga);
}

컴포넌트에서 사용 예시

const dispatch = useDispatch();
dispatch(fetchStart()); // abort
dispatch(fetchStart()); // abort
dispatch(fetchStart()); // good to go!

다 취소되고 마지막 요청만 실제 응답을 받아볼 수 있다.

참고하면 좋은 글
MDN: AbortController
Axios Docs: Cancellation
Kakao Tech: Promise는 왜 취소가 안 될까?

profile
안녕하세요

0개의 댓글