-
Notifications
You must be signed in to change notification settings - Fork 2
API 에러 처리하기
정재희 edited this page Nov 7, 2023
·
1 revision
- 에러 처리를 사전 정의된 기준에 따라 처리하고자 합니다.
- 일관성 있는 에러 처리를 하고자 합니다.
- 서버와 클라이언트 및 렌더링과 핸들러에서의 에러 처리 방안을 제안하고자 합니다.
아래와 같이 responseHandler를 정의하여 기존 rest로 얻은 response에 감싸 일관된 에러를 throw합니다.
private async responseHandler(response: Response): Promise<any> {
if (!response.ok) {
switch (response.status) {
case 401:
throw new UnauthorizedError(response)
case 403:
throw new ForbiddenError(response)
case 404:
throw new NotFoundError(response)
case 500:
throw new ServerError(response)
default:
throw new ApiError(response, 'An unexpected error occurred')
}
}
return await response.json()
}
예시
public async delete(
endpoint: string,
nextInit: RequestInit = {},
customHeaders: { [key: string]: string } = {},
): Promise<any> {
const response = await fetch(`${this.baseURL}${endpoint}`, {
method: 'DELETE',
headers: { ...this.headers, ...customHeaders },
...nextInit,
})
return this.responseHandler(response)
}
아래와 같이 네가지 에러 코드에 대한 에러를 사용합니다.
isInstanceOfApiError
를 통해 ApiError인지 확인할 수 있습니다.
import ErrorMessages from '@/config/errorMessages'
export class ApiError extends Error {
public statusCode: number
public statusText: string
public response: Response
constructor(response: Response, message?: string) {
super(message)
this.statusCode = response.status
this.statusText = response.statusText
this.response = response
this.name = 'ApiError'
}
}
export function isInstanceOfApiError(error: unknown): error is ApiError {
return error instanceof ApiError
}
export class NotFoundError extends ApiError {
constructor(response: Response, message?: string) {
super(response, message)
this.name = 'NotFoundError'
this.message = this.message ?? ErrorMessages.NotFound
}
}
export class ForbiddenError extends ApiError {
constructor(response: Response, message?: string) {
super(response, message)
this.name = 'ForbiddenError'
this.message = message ?? ErrorMessages.Forbidden
}
}
export class UnauthorizedError extends ApiError {
constructor(response: Response, message?: string) {
super(response, message)
this.name = 'UnauthorizedError'
this.message = this.message ?? ErrorMessages.Unauthorized
}
}
export class ServerError extends ApiError {
constructor(response: Response, message?: string) {
super(response, message)
this.name = 'ServerError'
this.message = this.message ?? ErrorMessages.ServerError
}
}
렌더링에서는 에러를 throw 해주고, 이에 근접한 page에서 error.tsx
파일을 통해 에러를 처리합니다.
관련 내용은 에러 핸들링을 참고해주세요.
주의: error.tsx
에서는 throw된 에러가 가려지고 일반 Error로 처리되기 때문에 error.message
를 통해 에러를 구분할 수 있습니다.
예시
'use client'
// Error components must be Client Components
import { useEffect } from 'react'
import { useRouter } from 'next/navigation'
import { useToast } from '@/components/ui/Toast/useToast'
import AppPath from '@/config/appPath'
import ErrorMessages from '@/config/errorMessages'
export default function ErrorPage({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
const router = useRouter()
const { toast } = useToast()
useEffect(() => {
console.log(error.digest, error.message, error.name)
if (error.message === ErrorMessages.Forbidden) {
console.log('ForbiddenError')
toast({
title: 'Forbidden',
description: 'You do not have permission to access this page.',
duration: 2000,
})
}
if (error.message === ErrorMessages.Unauthorized) {
router.push(AppPath.login())
toast({
title: 'Unauthorized',
description: 'Please login to access this page.',
duration: 2000,
})
}
if (error.message === ErrorMessages.NotFound) {
toast({
title: 'Not Found',
description: 'Please login to access this page.',
duration: 2000,
})
}
}, [error, router, toast])
return (
<div>
<h2>Something went wrong!</h2>
<p>
<strong>Error:</strong> {error.message} ({error?.name})
</p>
<button
onClick={
// Attempt to recover by trying to re-render the segment
() => reset()
}
>
Try again
</button>
</div>
)
}
렌더링이 아닌 핸들러에서의 비동기 동작으로 호출되는 api call은 ErrorBoundary로 전달되지 않습니다.
때문에 handleApiError
를 통해 얻은 값을 이용해 적절한 에러 처리를 할 수 있습니다.
const onClickButton = async () => {
try {
await getTest()
} catch (error) {
const { shouldRedirect, message } = handleApiError(error)
if (shouldRedirect) {
router.push(shouldRedirect)
} else {
console.log(shouldRedirect, error)
toast({
title: 'Error',
description: message,
duration: 1000,
})
}
}
}