[FIFAPulse] 개발기록 - TypeScript로 try .. catch 에러에 따른 조건부 로직 진행하기

조민호·2023년 4월 26일
1
post-custom-banner

모달창에서 입력한 닉네임에 대해 유효성 검사 진행하기


모달창에서 피파온라인 닉네임을 입력받을 때 2가지의 경우에 대한 유효성 검사를 한다

  • 해당 닉네임이 실제로 피파온라인 닉네임으로 존재하는가
  • 해당 닉네임이 이 웹에서 이미 다른 유저와 연동이 되어있는가

이 과정을 try catch를 통한 에러처리로 진행한다


AskNickNameModal.tsx

import React, { useState } from 'react';
import { useModalAPI } from '../../Context/Modal/ModalContext';
import { authService } from '../../../firebase';
import { collection, addDoc, getDocs, updateDoc, doc } from 'firebase/firestore';
import { dbService } from '../../../firebase';
import FIFAData from '../../Services/FifaData';
import { useUserObjAPI } from '../../Context/UserObj/UserObjContext';
import { getErrorMessage, getErrorName } from '../../utils/getErrorMessage';

const AskNickNameModal = () => {
  const [nickNameInput, setNickNameInput] = useState('');
  const { closeModal } = useModalAPI()!;
  const { setUserObj } = useUserObjAPI()!;

  const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { name, value } = e.target;
    setNickNameInput(value);
  };

  const closeModalAndGotoHome = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();

    const getData = async () => {
      try {

		    **// 해당 닉네임이 이미 이 웹에서 사용되고 있는지 확인
        // 만약 이미 DB에 존재한다면 error를 throw**
        const dbInfo = await getDocs(collection(dbService, 'userInfo'));
        dbInfo.forEach((i) => {
          if (i.data().nickname === nickNameInput) {
            throw new SyntaxError('이미 존재하는 계정입니다');
          }
        });

				**// 해당 닉네임이 피파온라인 계정에 존재하지 않는다면
        // 아래의 api호출에서 404 axios error를 발생**
        const fifa = new FIFAData();
        const result = await fifa.getUserId(nickNameInput);

        let obj = {
          googleUID: String(authService.currentUser?.uid),
          FIFAOnlineAccessId: result.accessId,
          level: result.level as unknown as number,
          nickname: result.nickname,
        };

        await addDoc(collection(dbService, 'userInfo'), {
          ...obj,
        });
        setUserObj(obj);
        closeModal();
        alert('등록이 완료되었습니다!');
      } catch (error) {

				**// 에러 이름과 에러 메세지를 유틸 함수로부터 받아옴
				// 에러 이름을 기반으로 서로 다른 경고 메세치 출력**
        const message = getErrorMessage(error);
        const errorName = getErrorName(error);

        if (errorName === 'AxiosError') {
          alert('해당 계정이 존재하지 않습니다. 다시 한번 입력 해 주세요!');
        }
        if (errorName === 'SyntaxError') {
          alert(message);
        }

        setNickNameInput('');
      }
    };

    getData();
  };
  return (
    <div>
      실제 피파온라인 계정과 연동이 필요합니다 피파온라인에서 사용하고 계시는 닉네임을 입력 해 주세요!
      <form onSubmit={closeModalAndGotoHome}>
        <input onChange={onChange} value={nickNameInput} />
        <input type="submit" value="확인 후 닫기" />
      </form>
    </div>
  );
};

export default AskNickNameModal;

위의 과정에서 try 안에서 2개의 조건을 검사하고 만약 하나라도 걸리는 게 있다면

바로 catch로 넘어가서 경고창을 띄우고 ,

입력창 상태를 빈 문자열로 상태 업데이트를 진행해서

화면을 다시 리렌더링 시켜서 재입력을 요구하는 방식이다

  • 여기서 axios error의 경우 자동으로 catch로 이동하지만

  • 이미 중복된 닉네임이 DB에 존재하는 경우 명시적으로 에러를 throw해줘야 한다

await 다음줄에 if (!result) { throw new Error ()} 이런 방식으로 반환 값에 대해 확인을 하면서 에러를 잡는 것 자체가 불가능하다
애초에 await자체에서 에러가 발생하면 바로 catch로 넘어가므로 나머지 로직은 실행조차 안 되므로 반환값을 확인조차 못 하는 것이다



TypeScript로 에러타입에 따른 로직 적용해보기


위의 로직을 보다 보면

	// 에러 이름과 에러 메세지를 유틸 함수로부터 받아옴
	// 에러 이름을 기반으로 서로 다른 경고 메세치 출력
	const message = getErrorMessage(error);
	const errorName = getErrorName(error);

이러한 부분을 볼 수 있다

주석에 기재 했듯이 , 에러 이름과 에러 메세지를 기존 JS방식처럼 에러객체로부터

받아오는 것이 아니라 별도의 로직을 통해 반환받는 것이다


JS에서는 catch문에서 인자로 전달받는 error에 대해 아무런 타입이 필요가 없었지만

TypeScript는 error 의 타입을 unknown을 기본값으로 갖기 때문에

TS에서는 타입 지정이 필요하다

그러므로 아래와 같이 일반 JS처럼 진행하게 된다면 에러가 발생한다

let json = '{ "age": 30 }';

try {
  let user = JSON.parse(json);

  if (!user.name) {
    throw new SyntaxError('name 프로퍼티가 존재하지 않습니다');
  }

  console.log(json); // 이 부분은 실행 x
  
} catch (error) {
  console.log(error.message); // Error 
	// ('e'은(는) 'unknown' 형식입니다.)
}

TS에서 error의 타입이 unknown인 이유는

throw 된 값은 에러가 아닌 아무 타입이나 던져질 수 있기 때문이다

실제로 JS에서도 아래와 같이 에러가 아닌 것들도 throw가 가능하다

throw "What the!?";
throw 7;
throw { wut: "is this" };
throw null;
throw new Promise(() => {});
throw undefined;

그러므로 이에 대한 해결책으로, 아래와 같이 작성할 수 있다
let json = '{ "age": 30 }';

try {
  let user = JSON.parse(json);

  if (!user.name) {
    throw new Error('name 프로퍼티가 존재하지 않습니다');
  }

  console.log(json); // 이 부분은 실행 x
} catch (error) {
  **let message;
  if (error instanceof Error) message = error.message;
  else message = String(error);**

  console.log(message);
}

만약 error가 실제로 Error 객체가 아니라면

에러를 stringify 하여 어느 단서라도 문자열로 출력하도록 한 것이다

이 로직을 모든 catch 블록에서 사용할 수 있는 유틸 함수로도

만들어줄 수 있다

function getErrorMessage(error: unknown) {
  if (error instanceof Error) return error.message;
  return String(error);
}

let json = '{ "age": 30 }';

try {
  let user = JSON.parse(json);

  if (!user.name) {
    throw new Error('name 프로퍼티가 존재하지 않습니다');
  }

  console.log(json); // 이 부분은 실행 x
} catch (error) {
  console.log(**getErrorMessage(error)**);
}

출처 :

TypeScript에서 catch block error message 사용하기

post-custom-banner

0개의 댓글