From 4acc6f33c8353b3972cd6e2d88f6f619f5e4cbb8 Mon Sep 17 00:00:00 2001 From: Kim Da Eun Date: Wed, 31 Jul 2024 09:21:38 +0900 Subject: [PATCH] =?UTF-8?q?docs:=20=ED=94=84=EB=A1=A0=ED=8A=B8=EC=97=94?= =?UTF-8?q?=EB=93=9C=20=EC=95=A0=ED=94=8C=EB=A6=AC=EC=BC=80=EC=9D=B4?= =?UTF-8?q?=EC=85=98=EC=9D=98=20=EC=98=A4=EB=A5=98=20=EC=B2=98=EB=A6=AC=20?= =?UTF-8?q?=EB=A7=88=EC=8A=A4=ED=84=B0=ED=95=98=EA=B8=B0:=20=EC=A2=85?= =?UTF-8?q?=ED=95=A9=20=EA=B0=80=EC=9D=B4=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...tend-Applications-A-Comprehensive-Guide.md | 418 ++++++++++++++++++ 1 file changed, 418 insertions(+) create mode 100644 July/article/Mastering-Error-Handling-in-Frontend-Applications-A-Comprehensive-Guide.md diff --git a/July/article/Mastering-Error-Handling-in-Frontend-Applications-A-Comprehensive-Guide.md b/July/article/Mastering-Error-Handling-in-Frontend-Applications-A-Comprehensive-Guide.md new file mode 100644 index 0000000..498dacf --- /dev/null +++ b/July/article/Mastering-Error-Handling-in-Frontend-Applications-A-Comprehensive-Guide.md @@ -0,0 +1,418 @@ +## πŸ”— [Mastering Error Handling in Frontend Applications: A Comprehensive Guide](https://medium.com/carousell-insider/mastering-error-handling-in-frontend-applications-a-comprehensive-guide-2df73846385b) + +### πŸ—“οΈ λ²ˆμ—­ λ‚ μ§œ: 2024.07.31 + +### 🧚 λ²ˆμ—­ν•œ 크루: λ ›μ„œ(김닀은) + +--- + +## ν”„λ‘ νŠΈμ—”λ“œ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ 였λ₯˜ 처리 λ§ˆμŠ€ν„°ν•˜κΈ°: μ’…ν•© κ°€μ΄λ“œ + +### μ†Œκ°œ + +μƒκΈ°λ°œλž„ν•œ μ‚¬μš©μž μΈν„°νŽ˜μ΄μŠ€μ™€ λˆˆλΆ€μ‹  κΈ°λŠ₯으둜 μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ΄ 생λͺ…을 μ–»λŠ” 맀혹적인 ν”„λ‘ νŠΈμ—”λ“œ 개발의 세계에 μ˜€μ‹  것을 ν™˜μ˜ν•©λ‹ˆλ‹€. κ·ΈλŸ¬λ‚˜ λͺ¨λ“  λͺ¨ν—˜κ³Ό λ§ˆμ°¬κ°€μ§€λ‘œ 이 여정에도 도전 κ³Όμ œκ°€ μ‘΄μž¬ν•©λ‹ˆλ‹€. κ°€μž₯ κ°•λ ₯ν•œ 적 쀑 ν•˜λ‚˜λŠ” 작기 μ–΄λ ΅κ³  μ˜ˆμΈ‘ν•  수 μ—†λŠ” β€œμ˜€λ₯˜β€μž…λ‹ˆλ‹€. 이 λΈ”λ‘œκ·Έ κ²Œμ‹œλ¬Όμ—μ„œλŠ” μ΄λŸ¬ν•œ 였λ₯˜λ₯Ό μ •λ³΅ν•˜κ³  ν”„λ‘ νŠΈμ—”λ“œ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ—μ„œ μ›ν™œν•œ μ‚¬μš©μž κ²½ν—˜μ„ μ°½μΆœν•˜κΈ° μœ„ν•œ 지식과 도ꡬλ₯Ό μ œκ³΅ν•˜κ² μŠ΅λ‹ˆλ‹€. + +
+ +![μ•„λ¦„λ‹€μš΄ ν”„λ‘œκ·Έλž¨μ΄ μ—λŸ¬μ—κ²Œ 곡격받고 μžˆμŠ΅λ‹ˆλ‹€.](https://miro.medium.com/v2/resize:fit:1400/format:webp/1*2ogOoE_T8Z6jsVtf8_xl0w.jpeg) + +
+ +### 1μž₯: μ˜μ›…λ“€μ„ λ§Œλ‚˜λ‹€ β€” CustomError와 κ·Έ λ™λ°˜μžλ“€ + +였λ₯˜λ₯Ό λ‹€λ£¨λŠ” μ—¬μ •μ—μ„œ, μš°λ¦¬λŠ” "CustomError"κ°€ μ΄λ„λŠ” λ―ΏμŒμ§ν•œ λ™λ°˜μžλ“€λ‘œ κ΅¬μ„±λœ νŒ€μ„ λ§Œλ“€λ©΄μ„œ μ‹œμž‘ν•©λ‹ˆλ‹€. 마치 λ§ˆλ²•μ‚¬μ˜ μ§€νŒ‘μ΄μ²˜λŸΌ, CustomErrorλŠ” λ‚΄μž₯ JavaScript "Error" 객체λ₯Ό ν™•μž₯ν•˜μ—¬ 우리의 μ• ν”Œλ¦¬μΌ€μ΄μ…˜ 고유의 μš”κ΅¬μ— 맞μΆ₯λ‹ˆλ‹€. "code"와 "data" 같은 λ§ˆλ²• 같은 속성듀을 톡해 였λ₯˜μ— λŒ€ν•œ μ •ν™•ν•œ 정보λ₯Ό μ „λ‹¬ν•˜μ—¬ 디버깅에 도움을 쀄 수 μžˆμŠ΅λ‹ˆλ‹€. + +
+ +```tsx +export class CustomError extends Error { + code; + data; + constructor(code: string, message: string, data?: any) { + super(message); + // target이 es6 미만이면 νƒ€μž…μŠ€ν¬λ¦½νŠΈμ—μ„œ 였λ₯˜κ°€ ν”„λ‘œν† νƒ€μž… 체인으둜 μ „λ‹¬λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. + // https://github.com/Microsoft/TypeScript-wiki/blob/main/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work + this.name = code; + this.code = code; + this.data = data; + } +} + +export function isCustomError(candidate: any): candidate is CustomError { + return candidate instanceof CustomError || 'code' in candidate; +} +``` + +ν•˜μ§€λ§Œ CustomErrorλŠ” 이 μ—¬μ •μ—μ„œ ν˜Όμžκ°€ μ•„λ‹™λ‹ˆλ‹€. 그것은 "AuthenticationError", "AuthorisationError", "NotFoundError"와 같은 특수 였λ₯˜ 클래슀 ꡰ단과 μ—¬λŸ¬ λΆ€λŒ€λ₯Ό μƒμ„±ν•©λ‹ˆλ‹€. 각 ν΄λž˜μŠ€λŠ” λͺ…ν™•ν•œ λͺ©μ μ„ 가지고 μžˆμ–΄ 였λ₯˜λ₯Ό μ‹λ³„ν•˜κ³  μš°μ•„ν•˜κ²Œ μ²˜λ¦¬ν•˜κΈ°κ°€ 더 μ‰¬μ›Œμ§‘λ‹ˆλ‹€. + +```tsx +export class NotFoundError extends CustomError { + constructor(message = '404 - Not Found', data?: any) { + super(ERROR_CODES.NOT_FOUND, message, data); + } +} + +export function isNotFoundError(candidate: any): candidate is NotFoundError { + return ( + candidate instanceof NotFoundError || + candidate?.code === ERROR_CODES.NOT_FOUND + ); +} +``` + +
+ +### 2μž₯: API λ ˆμ΄μ–΄ 였λ₯˜ 처리 λ§ˆμŠ€ν„°ν•˜κΈ° + +였λ₯˜ 처리 λ§ˆμŠ€ν„°λ₯Ό ν–₯ν•œ μ—¬μ •μ—μ„œ, μš°λ¦¬λŠ” API λ ˆμ΄μ–΄κ°€ μˆ˜ν–‰ν•˜λŠ” μ€‘μš”ν•œ 역할을 μžŠμ–΄μ„œλŠ” μ•ˆ λ©λ‹ˆλ‹€. 이 λ ˆμ΄μ–΄λŠ” μ™ΈλΆ€ μžμ›κ³Ό μ„œλΉ„μŠ€λ‘œ κ°€λŠ” κ΄€λ¬Έ 역할을 ν•˜λ―€λ‘œ, 였λ₯˜λ₯Ό 효과적으둜 μ²˜λ¦¬ν•˜λŠ” 것이 ν•„μˆ˜μ μž…λ‹ˆλ‹€. + +```tsx +const GeneralErrorMessage = + 'Error: Please refresh and try again, or contact the support team.'; + +export const errorHandler = (error) => { + const { request, response, code } = error; + if (code) { + if (code === 'ECONNABORTED') + throw new ServiceError('The server is taking too long to respond!'); + } + if (response) { + const { data, status } = response; + const readableError = getReadableError(status, data); + throw readableError; + } else if (request) { + // μš”μ²­μ€ λ³΄λ‚΄μ‘Œμ§€λ§Œ 응닡이 μ˜€μ§€ μ•Šμ•˜μ„ λ•Œ + throw new ServiceError(GeneralErrorMessage); + } else { + const { status } = { ...error.toJSON(), ...{ status: 500 } }; + const readableError = getReadableError(status); + throw readableError; + } +}; +const getReadableError = (status: number, data?: any) => { + if (status <= 500) { + if (status === 404) + return new NotFoundError(data?.errorMsg ?? ErrorMessages[404]); + if (status === 500) + return new ServiceError(data?.errorMsg ?? ErrorMessages[500]); + if (status === 401) + return new AuthenticationError(data?.errorMsg ?? ErrorMessages[401]); + if (status === 403) + return new AuthorisationError(data?.errorMsg ?? ErrorMessages[403]); + if (status === 409 || status === 422) + return new ValidationError(data?.errorMsg ?? ErrorMessages[409]); + return new ClientError(data?.errorMsg ?? ErrorMessages[status]); + } + return new ServiceError(data?.errorMsg ?? GeneralErrorMessage, data); +}; +``` + +우리의 API 였λ₯˜ ν•Έλ“€λŸ¬λŠ” 였λ₯˜ 객체λ₯Ό κ²€μ‚¬ν•˜κ³ , λ‹€μ–‘ν•œ 였λ₯˜ 상황을 μ²˜λ¦¬ν•˜λ©°, νŠΉμ • 상황에 λŒ€ν•΄ μ‚¬μš©μž μ •μ˜ 였λ₯˜ 클래슀λ₯Ό 던져 μΌκ΄€λ˜κ³  κ΅¬μ‘°ν™”λœ 였λ₯˜ 처리 μ ‘κ·Ό 방식을 보μž₯ν•©λ‹ˆλ‹€. + +
+ +### 3μž₯: μ™•κ΅­ 관리 β€” 였λ₯˜λ₯Ό μœ„ν•œ μƒνƒœ 관리 + +κ΄‘ν™œν•œ ν”„λ‘ νŠΈμ—”λ“œ 개발 μ™•κ΅­μ—μ„œλŠ” λ‹€μ–‘ν•œ 였λ₯˜λ₯Ό λ§Œλ‚˜κ²Œ λ©λ‹ˆλ‹€. μ§€ν˜œλ‘­κ³  μš°μ•„ν•˜κ²Œ ν†΅μΉ˜ν•˜κΈ° μœ„ν•΄μ„œλŠ” μ΄λŸ¬ν•œ 였λ₯˜λ₯Ό μ •λ°€ν•˜κ²Œ 관리해야 ν•©λ‹ˆλ‹€. 그럴 λ•Œ Redux와 같은 μƒνƒœ 관리 μ‹œμŠ€ν…œμ΄ ν•„μš”ν•©λ‹ˆλ‹€. + +
+ +![](https://miro.medium.com/v2/resize:fit:1400/format:webp/1*fBeBKJmxlnDIqlPS3BqsXA.jpeg) + +
+ +μƒνƒœ 관리λ₯Ό ν†΅ν•©ν•¨μœΌλ‘œμ¨ 였λ₯˜λ₯Ό μƒνƒœμ— μ›ν™œν•˜κ²Œ μ „νŒŒν•˜μ—¬ μ‚¬μš©μžμ—κ²Œ μ‹€μ‹œκ°„ ν”Όλ“œλ°±μ„ μ œκ³΅ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 예기치 μ•Šμ€ 였λ₯˜λ₯Ό μš°μ•„ν•˜κ²Œ μ²˜λ¦¬ν•˜λ“ , 정보성 λ©”μ‹œμ§€λ₯Ό μ œκ³΅ν•˜λ“ , 잘 κ΅¬μ‘°ν™”λœ 였λ₯˜ 처리 μ „λž΅μ€ μ‘°ν™”λ‘œμš΄ 왕ꡭ을 보μž₯ν•©λ‹ˆλ‹€. + +```tsx +export const errorSlice = createSlice({ + name: ERROR_SLICE_NAMESPACE, + initialState, + reducers: { + setError( + state, + { payload: { code, message, data } }: PayloadAction + ) { + state.code = code; + state.message = message; + state.data = data; + }, + clearError(state) { + const { code, message, data } = initialState; + state.code = code; + state.message = message; + state.data = data; + }, + }, +}); +``` + +
+ +### 4μž₯: μ „μž₯μ—μ„œ 였λ₯˜ κ°€λ‘œμ±„κΈ° β€” Redux Saga + +우리의 여정은 API 였λ₯˜κ°€ κ°•λ ₯ν•œ 적으둜 λ“±μž₯ν•˜λŠ” μ „μž₯으둜 μ΄μ–΄μ§‘λ‹ˆλ‹€. API 였λ₯˜ 처리λ₯Ό 쀑앙 μ§‘μ€‘ν™”ν•˜κΈ° μœ„ν•΄, μš°λ¦¬λŠ” "Redux Saga"λΌλŠ” ν˜„λͺ…ν•œ λ§ˆλ²•μ‚¬λ₯Ό ν˜ΈμΆœν•©λ‹ˆλ‹€. κ·Έ μ‹ λΉ„ν•œ λŠ₯λ ₯을 톡해 Redux SagaλŠ” HTTP μš”μ²­κ³Ό 응닡을 κ°€λ‘œμ±„κ³  곡톡 였λ₯˜ 처리 λ‘œμ§μ„ μ μš©ν•©λ‹ˆλ‹€. + +```tsx +const sagaTask = sagaMiddleware.run(saga); +sagaTask.toPromise().catch((error) => { + // error μƒνƒœμ— 따라 μ μ ˆν•œ μ•‘μ…˜μ„ λΆ€μ°©ν•˜μ„Έμš”. + store.dispatch(setError(error)); +}); +``` + +
+ +![](https://miro.medium.com/v2/resize:fit:1400/format:webp/1*nf6pFSS7GSvTcViwcRSBCw.jpeg) + +
+ +μ‹€μš©μ μΈ 예제둜 λ“€μ–΄κ°€ λ³΄κ² μŠ΅λ‹ˆλ‹€ β€” Redux Sagaλ₯Ό μ‚¬μš©ν•˜μ—¬ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ— μƒˆλ‘œμš΄ ν•­λͺ© μΆ”κ°€ν•˜κΈ°: + +
+ +```tsx +export function* addNewItem(action) { + const { + payload: { name }, + } = action; + + // UI μƒνƒœλ₯Ό μ—…λ°μ΄νŠΈν•˜μ—¬ ν•­λͺ© μΆ”κ°€κ°€ 진행 μ€‘μž„μ„ λ‚˜νƒ€λƒ…λ‹ˆλ‹€ + yield put(addNewItemPending()); + + // μƒˆλ‘œμš΄ ν•­λͺ©μ„ μΆ”κ°€ν•˜κΈ° μœ„ν•΄ APIλ₯Ό ν˜ΈμΆœν•©λ‹ˆλ‹€ + const data = yield call(ItemsClient.addNewItem, { + data: { name }, + }); + + if (data?.id) { + // 성곡 μƒνƒœλ‘œ UI μƒνƒœλ₯Ό μ—…λ°μ΄νŠΈν•©λ‹ˆλ‹€ + yield put(addNewItemSuccess()); + // μ‚¬μš©μžμ—κ²Œ 성곡 λ©”μ‹œμ§€λ₯Ό ν‘œμ‹œν•©λ‹ˆλ‹€ + yield put( + showToastMessageAction({ + type: 'success', + message: `Successfully added item: ${name}!`, + }) + ); + } else { + throw new IAMError("Couldn't add new item"); + } +} +``` + +
+ +이 μ˜ˆμ œμ—μ„œλŠ” Redux Sagaλ₯Ό μ‚¬μš©ν•˜μ—¬ μƒˆλ‘œμš΄ ν•­λͺ©μ„ μΆ”κ°€ν•©λ‹ˆλ‹€. UI μƒνƒœλ₯Ό μ—…λ°μ΄νŠΈν•˜κ³ , API ν˜ΈμΆœμ„ μˆ˜ν–‰ν•˜λ©°, 성곡 및 였λ₯˜ μ‹œλ‚˜λ¦¬μ˜€λ₯Ό μ²˜λ¦¬ν•©λ‹ˆλ‹€. Redux Sagaλ₯Ό 톡해 μ μ ˆν•œ 였λ₯˜λ₯Ό λ°œμƒμ‹œν‚¬ 수 있으며, μ΄λŸ¬ν•œ 였λ₯˜λ₯Ό Redux μŠ€ν† μ–΄μ— μ•‘μ…˜μœΌλ‘œ λ””μŠ€νŒ¨μΉ˜ν•˜κ³ , 였λ₯˜ μƒνƒœ 관리 μ†”λ£¨μ…˜μ„ μ‚¬μš©ν•˜μ—¬ 관리할 수 μžˆμŠ΅λ‹ˆλ‹€. 이λ₯Ό 톡해 였λ₯˜ 처리λ₯Ό ν†΅ν•©λœ λ°©μ‹μœΌλ‘œ μ œκ³΅ν•©λ‹ˆλ‹€. + +
+ +### 5μž₯: μ—λŸ¬ λ°”μš΄λ”λ¦¬ β€” μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ 방패 + +ν”„λ‘ νŠΈμ—”λ“œ 개발의 μ„Έκ³„μ—μ„œλŠ” μ—λŸ¬κ°€ μ• ν”Œλ¦¬μΌ€μ΄μ…˜ 전체λ₯Ό μ€‘λ‹¨μ‹œν‚¬ 수 μžˆλŠ” μœ„ν—˜ν•œ 쑴재둜 λ‚˜νƒ€λ‚  수 μžˆμŠ΅λ‹ˆλ‹€. λ‘λ €μ›Œν•˜μ§€ λ§ˆμ„Έμš”! μš°λ¦¬λŠ” "μ—λŸ¬ λ°”μš΄λ”λ¦¬"λΌλŠ” λ§ˆλ²•μ˜ 방패λ₯Ό μ†Œκ°œν•©λ‹ˆλ‹€. 이 μˆ˜ν˜Έμžλ“€μ€ Reactμ—μ„œ μ˜κ°μ„ λ°›μ•„ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ 일뢀λ₯Ό 감싸고, μ—λŸ¬μ˜ 치λͺ…적인 영ν–₯을 λ§‰μ•„μ€λ‹ˆλ‹€. + +
+ +```tsx +interface Props extends PropsFromRedux { + children: React.ReactNode; + ifNotAuthenticated: () => void; + onSupportClick: (errorMessage?: string) => void; +} + +class ModuleErrorBoundary extends Component { + state: { + error: Error | null; + errorInfo: ErrorInfo; + hasError: boolean; + }; + // λ Œλ”λ§ 쀑 μ—λŸ¬κ°€ λ°œμƒν–ˆμ„ λ•Œ ν˜ΈμΆœλ©λ‹ˆλ‹€ + static getDerivedStateFromError(error: Error) { + // λ‹€μŒ λ Œλ”λ₯Ό μœ„ν•œ μƒνƒœλ₯Ό μ„€μ •ν•©λ‹ˆλ‹€ + return { hasError: true, error }; + } + // μŠ€ν† μ–΄μ—μ„œ μ—λŸ¬κ°€ μ—…λ°μ΄νŠΈλ  λ•Œ ν˜ΈμΆœλ©λ‹ˆλ‹€ + static getDerivedStateFromProps(props: Props) { + const { error } = props; + logger.info(error); + if (error.code) return { hasError: true, error }; + return null; + } + constructor(props: Props) { + super(props); + this.state = { + hasError: false, + errorInfo: { componentStack: '' }, + error: null, + }; + } + // μ—λŸ¬λ₯Ό λ‘œκΉ…ν•©λ‹ˆλ‹€ + componentDidCatch(error: Error, errorInfo: ErrorInfo) { + logger.error(error); + logger.error(errorInfo.componentStack); + } + render(): ReactNode { + const { hasError, error } = this.state; + const { ifNotAuthenticated, children, onSupportClick } = this.props; + if (hasError) { + if (isAuthenticationError(error) && ifNotAuthenticated) + ifNotAuthenticated(); + const message = isCustomError(error) + ? error.message + : `Sorry, something went wrong +Try to refresh the page or contact your admin`; + return ( + <> + + + Error Image +

+ {message} +

+ {isNotFoundError(error) || isClientError(error) ? ( + + ) : ( + + )} +
+
+ + ); + } + return children; + } +} +const mapState = (state: AppState) => ({ error: state.ERROR_SLICE }); +const mapDispatch = { + clearError, +}; +const connector = connect(mapState, mapDispatch); +type PropsFromRedux = ConnectedProps; +export const ConnectedModuleErrorBoundary = connector(ModuleErrorBoundary); +``` + +
+ +μ—λŸ¬ λ°”μš΄λ”λ¦¬λ₯Ό μ‚¬μš©ν•˜λ©΄ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ΄ μ—λŸ¬λ₯Ό μš°μ•„ν•˜κ²Œ μ²˜λ¦¬ν•˜κ³ , μ‚¬μš©μž μΉœν™”μ μΈ λ©”μ‹œμ§€λ₯Ό ν‘œμ‹œν•˜λ©°, 볡ꡬ μ˜΅μ…˜μ„ μ œκ³΅ν•  수 μžˆμŠ΅λ‹ˆλ‹€. μ΄λŠ” 마치 μˆ™λ ¨λœ κΈ°μ‚¬μ²˜λŸΌ 왕ꡭ을 λ°©μ–΄ν•˜λŠ” 것과 κ°™μŠ΅λ‹ˆλ‹€. + +
+ +### 제 6μž₯: μ‹€μ „μ—μ„œμ˜ μ—λŸ¬ 처리 적용 + +ν”„λ‘ νŠΈμ—”λ“œ κ°œλ°œμ—μ„œ μ—λŸ¬ 처리λ₯Ό μœ„ν•œ 지식과 도ꡬλ₯Ό κ°–μΆ”μ—ˆμœΌλ‹ˆ, 이제 μ‹€μ œ 예제λ₯Ό 톡해 이λ₯Ό μ‹€μ „μ—μ„œ μ μš©ν•΄ λ³΄κ² μŠ΅λ‹ˆλ‹€. μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ— μƒˆλ‘œμš΄ ν•­λͺ©μ„ μΆ”κ°€ν•˜λŠ” 사가가 μžˆλ‹€κ³  κ°€μ •ν•΄ λ΄…μ‹œλ‹€. μš°λ¦¬κ°€ λ…Όμ˜ν•œ μ—λŸ¬ 처리 κΈ°μˆ μ„ μ–΄λ–»κ²Œ μ μš©ν•˜λŠ”μ§€ λ‹¨κ³„λ³„λ‘œ μ•ˆλ‚΄ν•΄ λ“œλ¦¬κ² μŠ΅λ‹ˆλ‹€. + +
+ +```tsx +export function* addNewItem(action: PayloadAction) { + try { + const { + payload: { name }, + } = action; + + // ν•­λͺ© μΆ”κ°€κ°€ 진행 μ€‘μž„μ„ λ‚˜νƒ€λ‚΄κΈ° μœ„ν•΄ UI μƒνƒœλ₯Ό μ—…λ°μ΄νŠΈν•©λ‹ˆλ‹€ + yield put(addNewItemPending()); + // μƒˆλ‘œμš΄ ν•­λͺ©μ„ μΆ”κ°€ν•˜κΈ° μœ„ν•΄ APIλ₯Ό ν˜ΈμΆœν•©λ‹ˆλ‹€ + const data = yield call(ItemsClient.addNewItem, { + data: { + name, + }, + }); + if (data?.id) { + // 성곡 μƒνƒœλ‘œ UI μƒνƒœλ₯Ό μ—…λ°μ΄νŠΈν•©λ‹ˆλ‹€ + yield put(addNewItemSuccess()); + // μ‚¬μš©μžμ—κ²Œ 성곡 ν† μŠ€νŠΈ λ©”μ‹œμ§€λ₯Ό ν‘œμ‹œν•©λ‹ˆλ‹€ + yield put( + showToastMessageAction({ + type: 'success', + message: `Successfully added item: ${name}!`, + }) + ); + // ν•­λͺ© ν…Œμ΄λΈ”μ˜ 데이터λ₯Ό μƒˆλ‘œ κ³ μΉ˜κ±°λ‚˜ ν•„μš”ν•œ λ‹€λ₯Έ μž‘μ—…μ„ μˆ˜ν–‰ν•©λ‹ˆλ‹€ + // (예: λ‹€λ₯Έ νŽ˜μ΄μ§€λ‘œ 이동) + // yield put(refreshItemsTableAction()); + } else { + // API 응닡에 'id'κ°€ ν¬ν•¨λ˜μ–΄ μžˆμ§€ μ•ŠμœΌλ©΄, μ‚¬μš©μž μ •μ˜ IAMErrorλ₯Ό λ°œμƒμ‹œν‚΅λ‹ˆλ‹€ + throw new IAMError("Couldn't add new item"); + } + } catch (err) { + // μ—λŸ¬λ₯Ό μ²˜λ¦¬ν•˜κ³  UI μƒνƒœλ₯Ό 적절히 μ—…λ°μ΄νŠΈν•©λ‹ˆλ‹€ + yield put(addNewItemFailed()); + // νŠΉμ • μ—λŸ¬ μœ ν˜• (IAMError, ValidationError λ“±)을 μ²˜λ¦¬ν•©λ‹ˆλ‹€ + if (!isIAMError(err) && !isValidationError(err)) { + // μ˜ˆμƒλœ μ—λŸ¬ μœ ν˜•μ΄ μ•„λ‹Œ 경우, μ—λŸ¬λ₯Ό λ‹€μ‹œ λ°œμƒμ‹œν‚΅λ‹ˆλ‹€ + throw err; + } + // μ—λŸ¬ μœ ν˜•μ— 따라 μ‚¬μš©μž μΉœν™”μ μΈ μ—λŸ¬ λ©”μ‹œμ§€λ₯Ό ν‘œμ‹œν•©λ‹ˆλ‹€ + if (isValidationError(err)) { + // μœ νš¨μ„± 검사 μ—λŸ¬ ν† μŠ€νŠΈ λ©”μ‹œμ§€λ₯Ό ν‘œμ‹œν•©λ‹ˆλ‹€ + yield put( + showToastMessageAction({ + type: 'error', + message: 'This item already exists!', + }) + ); + } else if (isIAMError(err)) { + // 일반 IAM μ—λŸ¬ ν† μŠ€νŠΈ λ©”μ‹œμ§€λ₯Ό ν‘œμ‹œν•©λ‹ˆλ‹€ + yield put( + showToastMessageAction({ + type: 'error', + message: "Couldn't add new item. Please try again later.", + }) + ); + } + } +} +``` + +
+ +이 μ˜ˆμ œμ—μ„œλŠ” μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ— μƒˆλ‘œμš΄ ν•­λͺ©μ„ μΆ”κ°€ν•˜λŠ” 사가가 μžˆμŠ΅λ‹ˆλ‹€. μ—¬κΈ°μ—μ„œ λ…Όμ˜ν•œ μ—λŸ¬ 처리 κΈ°μˆ μ„ λ‹€μŒκ³Ό 같이 μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€: + +- μš°μ„ , ν•­λͺ© μΆ”κ°€κ°€ 진행 μ€‘μž„μ„ λ‚˜νƒ€λ‚΄κΈ° μœ„ν•΄ UI μƒνƒœλ₯Ό μ—…λ°μ΄νŠΈν•©λ‹ˆλ‹€(addNewItemPending()). +- 그런 λ‹€μŒ, μƒˆλ‘œμš΄ ν•­λͺ©μ„ μΆ”κ°€ν•˜κΈ° μœ„ν•΄ API ν˜ΈμΆœμ„ ν•˜κ³ , API 응닡을 κ²€μ‚¬ν•˜μ—¬ μž‘μ—…μ΄ μ„±κ³΅ν–ˆλŠ”μ§€ ν™•μΈν•©λ‹ˆλ‹€. +- 응닡에 'id'κ°€ ν¬ν•¨λœ 경우, 성곡 μƒνƒœλ‘œ UI μƒνƒœλ₯Ό μ—…λ°μ΄νŠΈν•˜κ³ (addNewItemSuccess()), μ‚¬μš©μžμ—κ²Œ 성곡 ν† μŠ€νŠΈ λ©”μ‹œμ§€λ₯Ό ν‘œμ‹œν•˜λ©°, 데이터λ₯Ό μƒˆλ‘œ κ³ μΉ˜λŠ” λ“±μ˜ ν•„μš”ν•œ λ‹€λ₯Έ μž‘μ—…μ„ μˆ˜ν–‰ν•  수 μžˆμŠ΅λ‹ˆλ‹€. +- 응닡에 'id'κ°€ ν¬ν•¨λ˜μ§€ μ•Šμ€ 경우, νŠΉμ • μ—λŸ¬ μΌ€μ΄μŠ€λ₯Ό μ²˜λ¦¬ν•˜κΈ° μœ„ν•΄ μ‚¬μš©μž μ •μ˜ IAMErrorλ₯Ό λ°œμƒμ‹œν‚΅λ‹ˆλ‹€. 이λ₯Ό 톡해 μ—λŸ¬λ₯Ό μš°μ•„ν•˜κ²Œ κ΄€λ¦¬ν•˜κ³  μ‚¬μš©μž μΉœν™”μ μΈ ν”Όλ“œλ°±μ„ μ œκ³΅ν•©λ‹ˆλ‹€. +- catch λΈ”λ‘μ—μ„œλŠ” μ—λŸ¬λ₯Ό μ²˜λ¦¬ν•˜κ³ , μ‹€νŒ¨ μƒνƒœλ‘œ UI μƒνƒœλ₯Ό μ—…λ°μ΄νŠΈν•˜λ©°(addNewItemFailed()), μ—λŸ¬ μœ ν˜•(μœ νš¨μ„± 검사 μ—λŸ¬ λ˜λŠ” IAM μ—λŸ¬)에 따라 μ‚¬μš©μž μΉœν™”μ μΈ μ—λŸ¬ λ©”μ‹œμ§€λ₯Ό ν‘œμ‹œν•©λ‹ˆλ‹€. +- μ˜ˆμƒμΉ˜ λͺ»ν•œ μ—λŸ¬κ°€ 발견되면 λ‹€μ‹œ λ°œμƒμ‹œν‚΅λ‹ˆλ‹€. 이 μ—λŸ¬λŠ” 사가 미듀웨어에 μ˜ν•΄ 작히고, μ•‘μ…˜μ„ λ””μŠ€νŒ¨μΉ˜ν•˜μ—¬ μƒνƒœλ₯Ό μ—…λ°μ΄νŠΈν•©λ‹ˆλ‹€. 이 μƒνƒœλŠ” μ—λŸ¬ λ°”μš΄λ”λ¦¬μ— μ˜ν•΄ κ΅¬λ…λ˜λ©°, 이에 따라 μ˜¬λ°”λ₯Έ μ—λŸ¬ λ·°λ₯Ό ν‘œμ‹œν•  수 μžˆμŠ΅λ‹ˆλ‹€. + +이 예제λ₯Ό λ”°λ₯΄λ©΄, λ…Όμ˜ν•œ μ—λŸ¬ 처리 κΈ°μˆ μ„ ν”„λ‘ νŠΈμ—”λ“œ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ μ‹€μ œ μ‹œλ‚˜λ¦¬μ˜€μ— μ μš©ν•˜μ—¬ λΆ€λ“œλŸ¬μš΄ μ‚¬μš©μž κ²½ν—˜κ³Ό κ²¬κ³ ν•œ μ—λŸ¬ 관리λ₯Ό 보μž₯ν•  수 μžˆμŠ΅λ‹ˆλ‹€. + +
+ +![](https://miro.medium.com/v2/resize:fit:1400/format:webp/1*QyyaPwiMnQb4Bi4TgasVDw.jpeg) + +
+ +### κ²°λ‘ : μ˜μ›…μ˜ μ—¬μ • + +ν”„λ‘ νŠΈμ—”λ“œ 개발의 λ§ˆλ²• 세계λ₯Ό νƒν—˜ν•˜λ©΄μ„œ, μ—¬λŸ¬λΆ„μ€ κ°•λ ₯ν•œ "CustomError"와 κ·Έ λ™λ£Œλ“€μ„ 닀루고, μ—λŸ¬ λ°”μš΄λ”λ¦¬λ‘œ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ„ λ³΄ν˜Έν•˜λ©°, Redux Saga의 νž˜μ„ ν™œμš©ν•˜κ³ , μƒνƒœ 관리λ₯Ό 톡해 왕ꡭ을 λ‹€μŠ€λ¦¬λŠ” 법을 λ°°μ› μŠ΅λ‹ˆλ‹€. + +ν”„λ‘ νŠΈμ—”λ“œ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ—μ„œ μ—λŸ¬ 처리λ₯Ό λ§ˆμŠ€ν„°ν•˜λŠ” 것은 λ‹¨μˆœνžˆ μ—λŸ¬λ₯Ό λ¬Όλ¦¬μΉ˜λŠ” κ²ƒλ§Œμ΄ μ•„λ‹ˆλΌ, μ‚¬μš©μž κ²½ν—˜μ„ ν–₯μƒμ‹œν‚€κ³  μ†Œν”„νŠΈμ›¨μ–΄μ˜ μ•ˆμ •μ„±μ„ 보μž₯ν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€. μ΄λŸ¬ν•œ 도ꡬ와 κΈ°μˆ μ„ κ°–μΆ”κ²Œ 된 μ—¬λŸ¬λΆ„μ€ 이제 ν”„λ‘ νŠΈμ—”λ“œ μ›λ”λžœλ“œμ—μ„œ λ§ˆμ£ΌμΉ˜λŠ” μ–΄λ–€ μ—λŸ¬λ„ 헀쳐 λ‚˜κ°ˆ μ€€λΉ„κ°€ λ˜μ—ˆμŠ΅λ‹ˆλ‹€. + +λ‹€μŒ λΈ”λ‘œκ·Έ μ‹œλ¦¬μ¦ˆμ—μ„œλŠ” μ—λŸ¬ λ‘œκΉ… 및 보고의 μˆ¨κ²¨μ§„ μ˜μ—­μ„ νƒν—˜ν•˜λŠ” 여정을 μ‹œμž‘ν•©λ‹ˆλ‹€. ν”„λ‘ νŠΈμ—”λ“œ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ— μˆ¨μ–΄ μžˆλŠ” μ—λŸ¬μ— λŒ€ν•œ κ·€μ€‘ν•œ 톡찰λ ₯을 λ°œκ²¬ν•˜λŠ” 여정에 ν•¨κ»˜ν•˜μ„Έμš”. κ·Έλ•ŒκΉŒμ§€, ν–‰λ³΅ν•œ μ½”λ”© λ˜μ‹œκΈΈ 바라며, μ—¬λŸ¬λΆ„μ˜ μ½”λ“œκ°€ 버그 없이 κΉ¨λ—ν•˜κΈ°λ₯Ό λ°”λžλ‹ˆλ‹€!