react native에서 비동기함수 디버깅하기 -1

고병찬·2024년 9월 29일

TIL

목록 보기
35/54

들어가며

로그인 과정을 커스텀 훅으로 뺐는데 개선할 점이 더 많은 것 같다. 하지만 그전에 로그인이 제대로 동작하지 않는 문제가 있다.

  const handleGoogleLoginToken = async token => {
    try {
      const loginResponse = await api.googleLogin({ token, deviceToken });
      ...
      const user = await api.getUserInfo();
      ...

      return { accessToken: loginResponse.access, userId: user.id };
    ...

커스텀 훅 내부에 대략 이런 함수가 있다. handleGoogleLoginToken 함수는 구글 OAuth 인증을 통해 받은 구글 액세스 토큰을 우리 api 서버로 보내서 우리 서버에서 발급한 액세스 토큰을 받아오고 저장해둔다.
그리고 api.getUerInfo()에서 유저 정보를 받아오는데 이때 저장된 엑세스 토큰을 사용한다.

문제는 const user = await api.getUserInfo(); 이 코드 라인이 제대로 동작하지 않는다. 만약 더 api 요청 과정에서 잘못된게 있다면 try catch에 잡혀야 하는데 그것도 아니다. 그리고 원인 파악 과정에서 저 코드라인 앞 뒤로 콘솔을 찍어보면 저 코드라인 이후에 있는 콘솔은 찍히지가 않았다. 이유가 뭔지 모르겠어서 디버거를 통해 확인해보려고 한다.

디버깅하기

const user = await api.getUserInfo(); 라인에 중단점을 찍고 로그인 과정을 다시 시도해보았다.

멈춘 const user = await api.getUserInfo(); 이 코드라인에서 api.getUserInfo()가 내부에서 어떤 문제가 있는지 확인해야하기 때문에 step into로 함수 내부로 들어가보았다.


const getUserInfo = useCallback(() => {
    return handleRequest(() => axios.get(API_PATH.user, metadata())); <-------
  }, [handleRequest, metadata]);

return이 있는 코드라인으로 왔다. 한번더 들어가보아야할 것 같다.


const handleRequest = useCallback(
    async request => {
      try {
        const response = await request();
        return response.data;
      } catch (err) {
        if (
          (err.response?.status === 401 &&
            err.response?.data?.detail === TOKEN_INVALID_OR_EXPIRED_MESSAGE) ||
          err.response?.data?.detail === TOKEN_INVALID_TYPE_MESSAGE
        ) {
          try {
            생략...
      }
    }, <----------
    [accessToken],
  );

화살표로 표시해둔 곳으로 이동했다. handleRequest 함수는 api 요청 과정에서 엑세스 토큰이 만료되었을 때를 핸들링하는 함수이다. 한번더 step into 해보았다.


그랬더니 asyncToGenerator.js라는 곳으로 이동했다.

function asyncGeneratorStep(n, t, e, r, o, a, c) {
  try {
    var i = n[a](c),
      u = i.value;
  } catch (n) {
    return void e(n);
  }
  i.done ? t(u) : Promise.resolve(u).then(r, o);
}
function _asyncToGenerator(n) {
  return function () {
    var t = this,
      e = arguments;
    return new Promise(function (r, o) {
      var a = n.apply(t, e);
      function _next(n) {
        asyncGeneratorStep(a, r, o, _next, _throw, "next", n);
      }
      function _throw(n) {
        asyncGeneratorStep(a, r, o, _next, _throw, "throw", n);
      }
      _next(void 0);
    });
  };
}
module.exports = _asyncToGenerator, module.exports.__esModule = true, module.exports["default"] = module.exports;

Async 함수는 내부적으로 어떻게 동작할까?
AsyncGenerator-MDN
[JS]제네레이터란(Generator)?!
찾아보니 async가 지원되지 않는 환경에서도 동작할 수 있도록 해주는 함수였다. 이름대로 async를 generator로 바꿔주는 거다.

변수명이 알파벳으로만 적혀져있어 난감하지만 전체적인 흐름은 Async 함수는 내부적으로 어떻게 동작할까? 여기서 설명해준 것과 거의 유사하다.

    return new Promise(function (r, o) {
      var a = n.apply(t, e);
      function _next(n) {
        asyncGeneratorStep(a, r, o, _next, _throw, "next", n);
      }
      function _throw(n) {
        asyncGeneratorStep(a, r, o, _next, _throw, "throw", n);
      }
      _next(void 0);
    });

이 부분이 Async 함수는 내부적으로 어떻게 동작할까? 여기서 4번에 해당하는 것.


_next(void 0);를 실행했으니 asyncGeneratorStep(a, r, o, _next, _throw, "next", n);가 호출된다.

function asyncGeneratorStep(n, t, e, r, o, a, c) {
  try {
    var i = n[a](c),
      u = i.value;
  } catch (n) {
    return void e(n);
  }
  i.done ? t(u) : Promise.resolve(u).then(r, o);
}

Async 함수는 내부적으로 어떻게 동작할까? 여기서 5번에 해당한다.

블로그를 참고해서 보자면 key로 "next", argsvoid 0 이 들어왔으므로 generator 함수로 변환된 async 함수에서 첫번째 yeild가 나올때까지 코드를 실행한다.

근데 내 경우 뭐가 generator 함수로 변환된 것인지 헷갈린다.

  1. 이게 호출되고
const user = await api.getUserInfo();
  1. 그 다음 이게 호출되고
  const getUserInfo = useCallback(() => {
    return handleRequest(() => axios.get(API_PATH.user, metadata()));
  }, [handleRequest, metadata]);
  1. 그리고 토큰 갱신 처리를 하는 handleRequest에서 알 수 없게도 저 가장 마지막 부분 중괄호에서 step into를 누르니 asyncToGenerator로 간건데.... 일단 더 눌러봐야겠다.
const handleRequest = useCallback(
    async request => {
      try {
        const response = await request();
        return response.data;
      } catch (err) {
        if (
          (err.response?.status === 401 &&
            err.response?.data?.detail === TOKEN_INVALID_OR_EXPIRED_MESSAGE) ||
          err.response?.data?.detail === TOKEN_INVALID_TYPE_MESSAGE
        ) {
          try {
            생략...
      }
    }, <----------
    [accessToken],
  );

디버거에서 콜스택도 잠깐 봤다.

나는 useGoogleAuth에서 시작했는데 콜스택에서 show ignore-listed frames를 체크하니 되게 신기한 것들이 이미 쌓여있었다. 그래서 gpt에게 물어보니 React Native와 JavaScript 엔진의 내부와 관련된 것으로 내가 작성한 코드에 집중하는게 좋다고 한다.

그렇게 치면 지금 보고있는 asyncToGEnerator도 회색처리되어있는데 별로 안중요한건가?

const user = await api.getUserInfo();

여기서 step into가 아니라 step over로 user에 값이 잘 들어오는지부터 봐야겠다.


const user = await api.getUserInfo();

여기서 step over 하니까 다시 asyncGenerator로 들어왔다. 일단 asyncGeneratorStep에 온걸보니 비동기 과정에서 yeild마다 멈춰가며 함수를 실행하는 과정인 것 같다. 일단 결과를 확인해야 하니 step out으로 함수를 나가보겠다.


step out을 한번하니 asyncToGenerator로 가서 한번더 step out을 했더니 갑자기 JSTimer라는 곳으로 왔다.

계속 step out을 눌러도 제자리에 있다가 어느순간 여기로 왔다.


일단 계속 step out을 눌렀다. 그랬더니 어느순간 아래에서 모든게 끝났다.

도대체 뭐지...?
다시 한번 천천히 해봐야겠다.

profile
안녕하세요, 반갑습니다.

0개의 댓글