비동기 에러 헨들링

Imnottired·2023년 5월 26일
0
post-thumbnail

이상형 월드컵 프로젝트 진행하고 있는데 실수가 있었다.
우리는 월드컵 1. 정보 입력 -> 2.후보 등록 2단계로 이루어지는데,
post 2단계 값을 1단계 넣어서 오류가 발생하였고, 이로 인해 새로운 문제를 발견하였다.
그래서 이를 정리하고 다양한 방법을 시도함으로서 비동기에 대해 이해도를 높여보겠다.




위 그림에서 정보를 입력하고 저장을 누르면
데이터를 저장하고 데이터를 인지하고 2단계로 넘어간다.
하지만 코드를 잘못 수정하여서 1단계에서 문제가 발생하였고,
오류를 뱉게 되었다.

이런 오류를 뱉었음에도 저장(오류 내용을 저장)은 되었고 2단계로 넘어가게 되었다.
그럼 원하던 기존의

이 값들이 아니라 오류값들을 갖고 있기때문에,
2단계에서 문제가 발생하게 되었고, 1단계에서 문제가 발생했을 시
2단계로 넘어가지 못하게 막고, 저장을 막아주는 방향으로 에러 핸들링을 시도하였다.

먼저 코드를 보면

export async function post_worldcup(accessToken: string, worldCup: Post_req) {
  const response = await fetch(`${BACK_URL}/worldcups`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${accessToken}`,
    },
    body: JSON.stringify(worldCup),
  });
  console.log(response);
  const data = await response.json();

  return data;
}


//호출 
      post_worldcup(accessToken!, worldcup!)
        .then((res) => {
          setWorldcup(res);
          console.log(res);
          setIsWord("2");
        })
        .catch((err) => {
          console.log(err);
        });

함수 내에서 에러 핸들링이 되지 않아서
무조건 then에 반응하게 되었고, catch는 작동하지 않았다.
response에 맞추어서 에러 핸들링을 하지 않았고, 문제가 발생하게 되었다.

이제 이 문제를 해결하기 위해 몇가지 시도를 해볼 것인데
2가지 파트로 나누어서 정리를 할 것이다.

  • 하나는 함수, 두번째는 호출 이후다.
    함수는 fetch 함수를 말하는 것이고, 호출이후는 import 에서 함수를 사용하는 것을 의미한다.

전에는 에러 핸들링을

ok를 통한 결과 반환 (함수 x, 호출이후 o)

함수

//프리온 보딩 3월 4회차 로그인 코드
export const getCurrentUserInfo = async (): Promise<User | null> => {
  const userInfoRes = await fetch(`${BASE_URL}/profile`, {
    method: "GET",
    headers: {
      "Content-Type": "application/json",
      credentials: "include",
    },
  });

  return userInfoRes.ok ? userInfoRes.json() : null;
};

호출 이후

  const fetchUserProfile = useCallback(async () => {
    const userProfileResponse = await getCurrentUserInfo();

    if (userProfileResponse === null) { //실패시 로그인 화면
      routeTo("/login");
      return;
    }

    setUserProfile(userProfileResponse); //성공시 저장
  }, []);

이런 식으로 처리해주었다.
그러면 return 값에 맞추어 처리해주는 방식이었다.
함수내에서는 에러핸들링이 없고, 결과를 반환하고,
호출 이후 처리하는 방식이었다.

하지만 내가 쓰는 코드에서는 then일 경우에는 저장, 2단계 이동을 하게 만들었고,
catch일 경우에는 에러를 보여주게 만들었다.

그래서 위와 같이 작성할 경우, 함수에서 에러핸들링을 못해주고 계속해서 저장과 2단계로 넘어가기때문에 적합하지 못하다.


그렇다면 호출 이후에 에러를 판독하면 어떨까?

호출 이후 핸들링 (함수 x, 호출이후 o)

기존 함수에서는 json 과정없이 값을 반환하고,
받은 값만 반환한다.
호출 이후 그 값을 판독하고 에러인지 아닌지 확인한다.

호출 이후


//호출
post_worldcup(accessToken!, worldcup!)
  .then((response) => {
    if (!response.ok) {//catch로 보냄
      throw new Error("HTTP error, status = " + response.status);
    }
    return response.json();// then으로 보냄
  })
  .then((res) => {
    setWorldcup(res);
    console.log(res);
    setIsWord("2");
  })
  .catch((err) => {//실패할 경우 저장하지 않아서 2단계로 가지 않는다.
    console.log(err);
  });

기존의 함수는 무조건 then으로 반환하고, 이후 호출한 함수에서 에러가 있는지 체크하고,
핸들링 해주는 방식이다.
컴포넌트에서 모두 작성하기 때문에 지저분하다.

재사용성이 높은 함수라면 이러한 방식으로 각기 다른 방식을 추가할 수 있으니
그런 경우에는 선택지가 될 수 있겠지만 원하는 방향은 아니었다.

try catch를 추가한 함수 핸들링 (함수 o, 호출이후 o)

함수

export async function post_worldcup(accessToken: string, worldCup: Post_req) {
  try {
    const response = await fetch(`${BACK_URL}/candidates`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${accessToken}`,
      },
      body: JSON.stringify(worldCup),
    });

    if (!response.ok) {
      throw new Error("HTTP error, status = " + response.status);
    }

    const data = await response.json();
    return data;
  } catch (error) {
    // 에러 처리
    console.log(error);
    throw error; // 필요에 따라 예외를 다시 던지거나 특정 값을 반환할 수 있습니다.
  }
}

//호출 이후
      post_worldcup(accessToken!, worldcup!)
        .then((res) => {
          setWorldcup(res);
          console.log(res);
          setIsWord("2");
        })
        .catch((err) => {// throw error 일때 then이 아닌 catch 로 간다.
          console.log(err);
        });
        
        
        
        

중간에 ok를 통해 문제를 체크하고
if문이 작동할 경우
throw new Error가 나오면 catch에서 핸들링 해준다.

그러면 throw error가 되어서 호출된 부분에서 then이 아닌 catch로 이동한다.

user 46번은 함수 내
Editor은 47번은 호출된 곳이다.

이 방식을 선호하는 이유는
함수 내에서 먼저 성공과 실패를 가르기 때문에,
호출 이후에 then은 성공관련된 코드, catch는 실패 관련된 코드만 작성할 수 있어서
생각하기가 편했다.

또한 then에서 저장과 2단계로 넘어가는 이벤트를 catch에선 작성하지 않았으니
이벤트 캔슬이 되었다.

^0^

try catch 오류를 발생하는 2가지 조건

1. 오류 감지

공부하면서 try catch에 대해 알게 된 것이 있다.

try {

  alert('try 블록 시작');  // (1) <--

  // ...에러가 없습니다.

  alert('try 블록 끝');   // (2) <--

} catch(err) {

  alert('에러가 없으므로, catch는 무시됩니다.'); // (3)

}

기존의 try catch를 이런 식으로 에러가 없을 경우에는 지나간다.

try {

  alert('try 블록 시작');  // (1) <--

  lalala; // 에러, 변수가 정의되지 않음!

  alert('try 블록 끝(절대 도달하지 않음)');  // (2)

} catch(err) {

  alert(`에러가 발생했습니다!`); // (3) <--

}

위처럼 lalala는 정의되지 않았기때문에 try 문 안에서 오류가 발생하고
catch로 넘어간다.
여지껏 나는 이러한 오류 발생을 인지하고 그에 맞춘 기능으로 try catch를 사용하였다.

오늘 쓴 코드의 경우
response값을 반환하고 그것이 오류는 아니었어서 catch가 작동하지 않았다.

export async function post_worldcup(accessToken: string, worldCup: Post_req) {
try{
  const response = await fetch(`${BACK_URL}/worldcups`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${accessToken}`,
    },
    body: JSON.stringify(worldCup),
  });
  console.log(response);
  const data = await response.json();

  return data;
  }
catch(error){
  throw error
  }
}

그래서 위 코드와 다르게 자동으로 오류 감지가 아닌
직접적으로 throw 연산자를 활용하여서 조건이 만족하면
catch로 넘어가도록 작성한 것이었다.

2.throw 연산자 적용

함수

export async function post_worldcup(accessToken: string, worldCup: Post_req) {
  try {
    const response = await fetch(`${BACK_URL}/candidates`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${accessToken}`,
      },
      body: JSON.stringify(worldCup),
    });

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


    const data = await response.json();
    return data;
  } catch (error) {
    // 에러 처리
    console.log(error);
    throw error; // 필요에 따라 예외를 다시 던지거나 특정 값을 반환할 수 있습니다.
  }
}


위와 같이 작성하여서 실패했을 때 status 값을 뱉게 하였고 그에 맞추어서
코드를 작성하였다.

그래서 위와 같이 내가 원하는 상황에 맞추어서 코드를 작성할 수 있었다




마무리

response 값에 맞추어서 에러 핸들링을 하거나
try catch를 사용해서 에러핸들링을 하거나
어떠한 방식을 사용하든 상황에 맞추어서 잘사용하면 될 것 같다.

특히 try catch에서 fetch가 실패하면 오류로 감지할 것이라고 생각하였는데,
그렇지 않다는 사실이 새로웠고,
throw를 통해 catch에서 내가 원하는 값을 뱉는 것이 만족스러웠다.

이제 유저에게 400이 뜨든 401이 뜨든 상황에 맞추어서 길안내를 해줄 수 있을 것 같다.
끗!

profile
새로운 것을 배우는 것보다 정리하는 것이 중요하다.

5개의 댓글

comment-user-thumbnail
2023년 5월 28일

try + throw와 catch는 한몸이죠! 통신 성공 시 if문으로 빠지게 하고 나머지는 필연적으로 에러기 때문에 마지막에 throw 던지는 식으로 적었던 기억이 있네요

답글 달기
comment-user-thumbnail
2023년 5월 28일

저도 throw 거의 안썼는데 이제 잘 사용해야겠네욤 ..

답글 달기
comment-user-thumbnail
2023년 5월 28일

딥다이브 읽으면서 에러 생성과 에러 발생은 의미가 다르다는 게 기억나네욥 잘 보고 갑니다 !

답글 달기
comment-user-thumbnail
2023년 5월 28일

수정한 코드가 가독성도 더 좋은 것 같아요!! 👍👍 잘 읽었습니다

답글 달기
comment-user-thumbnail
2023년 5월 28일

너무 좋은 글인 것 같아요 ~! 정신없이 읽었네요 :) 에러핸들링도 꼼꼼하게 해줘야하는데 ㅎㅎ... 반성하고 갑니다

답글 달기