API 함수 재귀로 업그레이드 하기

Imnottired·2023년 7월 17일
0

Project

목록 보기
3/5
post-thumbnail

어제 refreshToken을 사용하여 401를 대비하는 로직을 작성하였다.
하지만 아쉬웠던 점이 있었다.
함수 안에서 해결하는 것이 아니라 밖으로 꺼내어서

하나하나 명령하는 명령형 프로그래밍으로 작성하였다.
(명령형과 선언적 프로그래밍 관련 내용)

이는 컴포넌트 로직이 길어져서 함수안에서 Refresh 토큰이 자동으로 해결되게 하고 싶었다.
그래서 보여지는 부분을 줄이는 방법을 고민하였다.

기본 로직

useEffect(() => {
    if (accessToken) {
      fetchUserData(accessToken)
        .then((data) => {
          setUser(data);
          if (lastPathState) {
            router.replace(lastPathState); // 이전 페이지로 이동
            setlastPathState(null);
          }
        })
        .catch((error) => {
          if (error === 401) {
            post_refresh()
              .then((data: any) => {
                setAccessToken(data.data);
                console.log(data.data);
              })
              .catch((error) => {
                console.error(error);
              });
          }

          console.error(error);
        });


    } else if (router.query.access_token) {

      setAccessToken(router.query.access_token as string); // 토큰 저장
    } else if (!router.query.access_token && !lastPathState) {
      router.replace("/"); // 토큰이 없고 이전 경로가 없으면 메인으로 이동
    }
  }, [router.query.access_token, accessToken, setAccessToken]);

위와 같이 then,catch가 직접적으로 나와있고, 행동을 함수 밖에서 처리하는 식으로 작성하였다.

원하는 방향

내가 원하는 방향은 fetchUserData 안에서 상황에 맞추어서 refreshToken로 갱신을 해주고 우리가 목표로 하는 userData를 주는 것을 알아서 해주었으면 좋겠다고 생각했다.
그래서 이 안에서 해결하는 로직을 고민하였다.

fatchUserData 로직을 뜯어보면,

export async function fetchUserData(accessToken: string) {
  try {
    const response = await fetch(`${BACK_URL}/members`, {
      headers: {
        Authorization: `Bearer ${accessToken}`,
      },
    });
    if (!response.ok) {
      throw response.status;
    }
    const data = await response.json();
    return data;
  } catch (error) {
    console.log("하이");
    // 에러 처리
    console.log(error);
    throw error; // 필요에 따라 예외를 다시 던지거나 특정 값을 반환할 수 있습니다.
  }
}

위처럼 작성하였다 여기서 나는 refresh가 작동으로 업데이트 되는 것을 고민하였다.
1차적으로 갱신을 해주어야한다고 생각하였다.

그래서 response이 업데이트 되고 난 후에

   if (response.status === 401) {
      // 토큰이 만료되었을 때
      const refreshedToken = await get_refresh(); // refresh 토큰 요청
      // refresh 토큰을 사용하여 다시 요청

    } else if (!response.ok) {
      throw response.status;
    }

위와 같이 상황에 맞추어서 refresh를 업데이트 하는 것을 생각하였다.

get_refresh 로직

export async function get_refresh() {
  try {
    const response = await fetch(`${BACK_URL}/auth/refresh`, {
      method: "GET",
      headers: {
        "Content-Type": "application/json",
      },
      credentials: "include",
    });

    if (!response.ok) {
      throw response.status;
    }
    const data = await response.json();

    return data;
  } catch (error) {
    // 에러 처리
    console.log(error);
    throw error;
  }
}

하지만 문제는 업데이트한 토큰을 어디다 저장하고
다시 한번 로직을 돌릴지 방법이 생각나지 않았다.

그래서 활용한 방법이 바로 재귀였다.
시나리오를 얘기하자면
401일 경우 refreshToken을 갱신하고,
이후에 return 값으로 해당 함수와 매개변수를 넘겨주어서
새로운 토큰을 통해 갱신하고 값을 리턴해주는 것이다.

그러면 기존의 함수에서는
토큰 갱신이 이루어진지도 모르고, 본인이 원하는 결과값만 받을 수 있어서
깔끔하게 로직을 작성할 수 있다.

실제 코드로 보자.

export async function fetchUserData(accessToken: string) {
//생략
    if (response.status === 401) {
      // 토큰이 만료되었을 때
      const refreshedToken = await get_refresh(); // refresh 토큰 요청
      // refresh 토큰을 사용하여 다시 요청
      return fetchUserData(refreshedToken.data);
      //refreshedToken.data는 토큰을 의미한다.
    } else if (!response.ok) {
      throw response.status;
    }
//생략
}

위와 같이 토큰을 갱신하고, 바로 그값을 재귀로 넘겨주어서
refreshToken을 자연스럽게 플로우에 넣었다.

그리고 실제 이함수를 호출하는 컴포넌트를 보면

   if (accessToken) {
      fetchUserData(accessToken)
        .then((data) => {
          setUser(data);
          if (lastPathState) {
            router.replace(lastPathState); // 이전 페이지로 이동
            setlastPathState(null);
          }
        })
        .catch((error) => {
          // if (error === 401) {
          //   post_refresh()
          //     .then((data: any) => {
          //       setAccessToken(data.data);
          //       console.log(data.data);
          //     })
          //     .catch((error) => {
          //       console.error(error);
          //     });
          //   console.log("로그인 해야해~");
          // }

          console.error(error);
        });

위와 같이 주석으로 처리한 만큼 생략할 수 있어서
좀 더 가독성 좋은 로직으로 바뀌었다.




끝으로

알고리즘을 공부할 때는 생각을 넓히기 위해서 사용한다 생각하였는데,
실제 이 로직에 활용되었다는 점이 좋았다.

어제만해도 함수 안에서 해결 못할 것이라고 생각하였는데
오랜 고민 끝에 액세스 토큰을 다시 가져와서 사용할 수 있는 로직을 완성한 것이 만족스럽다.


재귀함수 탈출 조건 추가

재귀를 쓴다면 무한루프를 경계해야한다. 그래서 탈출 조건까지 추가하여서 수정하였다.

const fetcherPost = async (
	url: string,
	Candidatedata: any,
	token?: string,
	retryCount: number = 0
) => {
	try {
		const response = await fetch(url, {
			method: "POST",
			headers: {
				"Content-Type": "application/json",
				"Cache-Control": "max-age=600",
				...(token && { Authorization: `Bearer ${token}` })
			},
			body: JSON.stringify(Candidatedata)
		});
		if (response.status === 401) {
			// 토큰이 만료되었을 때
			if (retryCount >= 3) {
				throw new Error("토큰을 갱신할 수 없습니다.");
			}
			const refreshedToken = await get_refresh(); // refresh 토큰 요청
			// refresh 토큰을 사용하여 다시 요청
			return fetchUserData(refreshedToken.data as string, retryCount + 1);
		} else if (!response.ok) {
			throw response.status;
		}
		const data = await response.json();
		return data;
	} catch (error) {
		console.log(error);
		throw error;
	}
};
profile
많이 배우려고 하고 합니다. 아쉬운 점이 있으면 말씀해주시면 감사하겠습니다.

4개의 댓글

comment-user-thumbnail
2023년 7월 17일

덕분에 좋은 정보 얻어갑니다, 감사합니다.

답글 달기
comment-user-thumbnail
2023년 7월 21일

재귀를 실제 로직에 사용하다니.. 새롭네요 ㅎㅎ뿌듯하셨을 것 같아요!!

답글 달기
comment-user-thumbnail
2023년 7월 23일

재귀는 알고리즘 풀 때만 사용해봤는데 이렇게 사용하시다니..! 고생하셨습니다 ㅎ

답글 달기
comment-user-thumbnail
2023년 7월 23일

오호 코드가 훨씬 깔끔해졌네요. 고생하셨습니다!

답글 달기