Error handling은 서비스 수준의 개발에 있어서, 매우 중요
Error handing을 잘 하면, 에러가 났을 경우, 빠르고 쉽게 원인을 파악하고 대처(해결)이 가능
- 자바스크립트에는
Error
Class가 있어서, 에러처리를 하고자 할 때에는 Error 인스턴스를 생성해서 throw 할수 있음- 추가적으로, Error Sub Types를 활용할 수 도 있음
:RangeError
,ReferenceError
,SyntaxError
,TypeError
,URIError
- 유의할 점은 throw 할 때 Error 인스턴스 생성 없이 곧바로 string 자체를 throw 하지 말 것! (왜냐하면, Error 인스턴스를 생성해야지 자동으로 stack 속성으로 tracking이 가능
: 반드시 throw만 사용해야할 필요는 없고, Error 인스턴스 객체를 넘겨주기만 하면 됨try { throw new Error('Something bad happened'); } catch(e) { console.log(e); }
- typescript로 (함수) 구현하는 경우, 명확하게 value와 return 값을 정의할 것 (아래 1번 X, 2번 O)
# 1 function validate(value: number) { if (value < 0 || value > 100) throw new Error('Invalid value'); } # 2 function validate(value: number): {error?: string} { if (value < 0 || value > 100) return {error:'Invalid value'}; }
- 직접 에러 객체(클래스)를 만들고, 타입가드를 활용하여 에러를 구분하여 타입스크립트 컴파일러의 도움을 받을 수 있음
- 직접 에러 객체(클래스) 만들기
class HTTPError extends Error { constructor(statusCode: number, message?: string) { super(message) // 반드시 호출해야함 this.name = `HTTPError` this.statusCode = statusCode } } const fetchPosts = async () => { const response = await fetch(`/api/posts`) if (response.ok) { return await response.json() } else { throw new HTTPError(response.status, response.statusText) } } const renderPosts = async () => { try { const posts = await fetchPosts() // Do something with posts } catch (e) { console.error(e.statusCode) // <- 컴파일 에러 //instanceof 는 자바스크립트에 내장되어 있는 value가 다른 value의 instance인지를 체크하는 operator if (e instanceof HTTPError) { alert(`fetching posts failed, error code is ${e.statusCode}`) // 이건 정상 } } }
- 에러 객체 내 에러를 처리하는 로직까지 포함시키도록 하기
class CustomError extends Error { name: string constructor(message?: string) { super(message) this.name = new.target.name Object.setPrototypeOf(this, new.target.prototype) Error.captureStackTrace && Error.captureStackTrace(this, this.constructor) // 하는 김에 스택트레이스도 바로잡아줍시다 } } class HTTPError extends CustomError { constructor( private statusCode: number, private errorData: Record<string, any>, message?: string ) { super(message) this.name = ‘HTTPError’ } get rawErrorData() { return this.errorData } get codeToErrorMessage() { switch (this.statusCode) { case 401: return `You don’t have a permission.` // ... } } } try { // ... } catch (e) { if (e instanceof HTTPError) { showErrorMessage(e.codeToErrorMessage) } }
Throw
에 의존하지 말기
: throw는 typesafe가 아니고, 어떤 값이라도 전달하게 되고, 이에 대한 명세(documentation)을 따로 작성하고 일일히 업데이트 해야 하는 불편type ResponseData = { statusCode: number responseBody?: ResponseBody } const makeHttpRequest = async (url: string): Promise<ResponseData> => { if (!isUrl(url)) { throw new Error( 'Invalid string passed into `makeHttpRequest`. Expected a valid URL.' ) } // ... // other business logic here // ... return { ... } // ResponseData }
Success
와Failure
의 경우로 나누어 미리 형식을 지정해 놓기
: ok는 T type을 가지는 인자를 받는 함수이고, err는 E type을 가지는 인자를 받는 함수로 정해짐type Result<T, E> = Ok<T, E> // contains a success value of type T | Err<T, E> // contains a failure value of type E
// utility functions to build Ok and Err instances const ok = <T, E>(value: T): Result<T, E> => new Ok(value) const err = <T, E>(error: E): Result<T, E> => new Err(error) const makeHttpRequest = async (url: string): Promise<Result<ResponseData, Error>> => { if (!isUrl(url)) { return err(new Error( 'Invalid string passed into `makeHttpRequest`. Expected a valid URL.' )) } // ... // other business logic here // ... return ok({ ... }) // Ok(ResponseData) }
try..catch..
를 이용 https://reactjs.org/docs/error-boundaries.html#gatsby-focus-wrapper
- 리액트에서 자바스크립트 에러를 해결하기 위해, React 16에서
error boundary
컨셉을 도입- 일종의 React component로서, child component tree의 어느곳에서 발생하는 자바스크립트 에러를 catch하고, loggin, fallback UI를 보여줌.
주의사항 (아래의 에러는 catch할 수 없음)
- Event Handler
- Asynchronouse Code
- Server Side rendering.
- boundary 그 자신에서 발생하는 에러
getDerivedStateFromError()
or componentDidCatch()
로 정의되면 error boundary
가 됨class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// You can also log the error to an error reporting service
logErrorToMyService(error, errorInfo);
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
https://elazizi.com/handling-errors-in-react-native-a-complete-guide
- JS or React 자체의 에러
- Native Module 측면에서의 에러
import * as React from "react";
import { ErrorBoundary } from "react-error-boundary";
import { View, StyleSheet, Button } from "react-native";
import { Text } from "components";
const myErrorHandler = (error: Error) => {
// Do something with the error
// E.g. reporting errorr using sentry ( see part 3)
};
function ErrorFallback({ resetErrorBoundary }) {
return (
<View style={[styles.container]}>
<View>
<Text> Something went wrong: </Text>
<Button title="try Again" onPress={resetErrorBoundary} />
</View>
</View>
);
}
export const ErrorHandler = ({ children }: { children: React.ReactNode }) => (
<ErrorBoundary FallbackComponent={ErrorFallback} onError={myErrorHandler}>
{children}
</ErrorBoundary>
);
react-native-exception-handler
와 같은 라이브러리 사용해서 처리할 수 있음Sentry
와 같은 클라우드 서비스 통해서, 실시간으로 추적할 수 있음