[Axios] 토큰 갱신 + 디바운스 => promise array

Darcy Daeseok YU ·2023년 4월 8일
0

Axios inteceptor token 갱신 + debounce

레거시 프로젝트에 토큰 갱신 인터셉터를 수정하다....

한페이지 내에서 호출이 여러번 들어오는데 호출 1개당 토큰리프레시 1회가 호출된다...

낭비 ...

디바운스 적용 ... Promise 객체를 써야할듯하다.

해결안 : 잘된다


// 지금 토큰 갱신 중인지
let isRefreshing = false;
// 여러개 호출 들어오는 경우 : 모두 토큰 갱신이 필요
let originalRequests = [];
apiClient.interceptors.response.use(
  function (response) {
    return response;
  },
  async error => {
    console.log(
      '%c interceptors.response ::: error : ',
      'background: black; color: crimson',
      error,
    );

    // axios 사용하기 때문 에러도 AxiosError 객체
    if (axios.isAxiosError(error)) {
      if (
        error.response.status === 401 &&
        error.response.data.code === AuthErrCodes.TOKEN_VERIFY_ERROR
      ) {
        const accessToken = sessionStorage.getItem('accessToken');
        const refreshToken = sessionStorage.getItem('refreshToken');

        if (!refreshToken)
          return Promise.reject('재 로그인이 필요합니다.[No Refresh Token]');

        const originalRequest = error.config;
        // 요청건이 처음 시도인지 체크
        if (!originalRequest._retry) {
          if (isRefreshing) {
            return new Promise(function (resolve, reject) {
              originalRequests.push({
                url: originalRequest.url,
                resolve,
                reject,
              });
            })
              .then(token => {
                originalRequest.headers['Authorization'] = 'Bearer ' + token;
                return axios(originalRequest);
              })
              .catch(err => {
                return Promise.reject(err);
              });
          }

          originalRequest._retry = true;
          isRefreshing = true;

          return new Promise(function (resolve, reject) {
            renewAuthTokens({ accessToken, refreshToken })
              .then(({ accessToken, refreshToken }) => {
                sessionStorage.setItem('accessToken', accessToken);
                sessionStorage.setItem('refreshToken', refreshToken);

                apiClient.defaults.headers.common['Authorization'] =
                  'Bearer ' + accessToken;
                originalRequest.headers['Authorization'] =
                  'Bearer ' + accessToken;

                originalRequests.forEach(originalRequestPromise => {
                  originalRequestPromise.resolve(accessToken);
                  console.log('처리됨 ::: ', originalRequestPromise.url);
                });

                resolve(axios(originalRequest));
              })
              .catch(err => {
                originalRequests.forEach(originalRequestPromise => {
                  originalRequestPromise.reject(err);
                });

                // 리덕스 예제
                // store.dispatch(showMessage({ message: 'Expired Token' }));

                reject(err);
              })
              .then(() => {
                isRefreshing = false;
              });
          });
        }
      }

디바운스는 위에 로직하고 섞어써야할듯하다. => 조금 미뤄둔다.
originalRequest =[] 에 promise객체 써주고 originalRequest._try 체크 해주면 될듯하다.


인터셉터 외부에 변수를 정의 
let timer = null;
let originalRequests = [];

apiClient.interceptors.response.use(
  function (response) {
    return response;
  },
  async error => {
    console.log(
      '%c인터셉터 에러 ==== ',
      'background: lightgreen; color: white',
      error,
    );

    // axios 사용하기 때문 에러도 AxiosError 객체
    if (axios.isAxiosError(error)) {
      if (
        error.response.status === 401 &&
        error.response.data.code === AuthErrCodes.TOKEN_VERIFY_ERROR
      ) {
        const authTokens = JSON.parse(
          sessionStorage.getItem('recoil-persist'),
        )?.auth;

        if (!authTokens.refreshToken)
          return Promise.reject('refreshToken을 불러올 수 없습니다.');

        // 디아운스 토큰 요청 한번에 요청 && 처리
        // 0.5s이내 토큰 리프레시 들어오면 요청중이던 토큰 리프레시 스탑 후 재실행
        const originalRequest = error.config;
        originalRequests.push(originalRequest);

        if (timer) {
          clearTimeout(timer);
        }

        timer = setTimeout(async () => {
          // token refresh 요청
          const data = await renewAuthTokens(authTokens);

          sessionStorage.setItem(
            'tokens',
            JSON.stringify({
              accessToken: data.accessToken,
              refreshToken: data.refreshToken,
            }),
          );

          setAxiosApiAuthToken(data.accessToken);

          // 토큰 에러 처리 후 요청 다시 진행
          originalRequests.map(async originalRequest => {
            originalRequest.headers[
              'Authorization'
            ] = `Bearer ${data.accessToken}`;

            //axios.reqeust(originalRequest) 쓰면 어레이 마지막 요청만 호출됨
            return apiClient(originalRequest);
          });

          //array비우기
          originalRequests.splice(0);
        }, 500);

        return;
      }

      if (error.status === 500) {
        console.log('%c error 500====', 'background-color: yellow');
        return alert(error.message);
      }
      console.log('response error', error);

      return Promise.reject(error);
    }
  },
);
profile
React, React-Native https://darcyu83.netlify.app/

0개의 댓글