Angular에서 오류 포착 및 처리

모든 애플리케이션에서 가장 반복적이고 따분한 작업 중 하나는 오류 처리입니다. 우리가 하고 싶은 것은 우리가 무언가를 놓쳤는지 아닌지에 대해 별로 생각하지 않고 오류를 잡아서 처리하는 습관 또는 패턴을 개발하는 것입니다. 이 게시물에서는 Angular에서 오류 처리를 구성하려고 합니다.

곤충의 일생



오류는 일반적으로 우리의 잘못이거나 다른 사람의 잘못입니다. 오늘 저는 후자에 관심이 있습니다. 타사 라이브러리 오류 및 API 관련 오류입니다. 비즈니스 계층에서 시작됩니다.
RxJS 연산자 또는 try ... catch 문을 통해 포착합니다. 비즈니스는 오류를 처리할 책임이 없으므로 오류를 수정한 후 다시 반환해야 합니다.

소비자 구성요소(UI 레이어)에서 오류를 포착하고 처리할 수 있습니다. 반응은 토스트 메시지, 리디렉션, 오류 스크롤, 대화 등이 될 수 있습니다. 항상 "조용히 처리"할 수 있습니다😏. 그렇게 하지 않으면 애플리케이션의 핵심에 있는 Angular Error Handler가 이를 로깅하고 아마도 추적기에 알려서 최종적으로 처리해야 합니다.

UI 대 백엔드 오류 메시지



API 서비스는 일반적으로 빌드 방법에 대한 전역적 이해가 있더라도 오류를 반환하는 고유한 방법이 있습니다. 백엔드에서 반환된 오류는 상황에 맞지 않으며 데이터베이스 개발자가 얼마나 자부심을 가지고 있는지에 관계없이 사용자에게 친숙하지 않습니다. 그들은 단순히 충분하지 않습니다. 다음 주에 토스트 메시지에 대해 이야기할 때 이를 증명할 예를 들겠습니다.

다행히 최근에는 서버 오류가 '코드'와 함께 반환되는 경우가 더 자주 발생합니다. UI에서 해당 코드를 사용하여 해당 오류 메시지를 다시 만들 수 있습니다.

먼저 거꾸로 작업하면 다음은 간단한 오류 메시지(요청된 API 포인트의)를 반환하는 호출을 수행하는 구성 요소의 예입니다.

create(project: Partial<IProject>) {
  // handling errors in a better way
  this.projectService.CreateProject(project).subscribe({
    next: (data) => {
      console.log(data?.id);
    },
    error: (error) => {
      // do something with error, toast, dialog, or sometimes, silence is gold
      console.log(error);
    }
  });
}

// in a simpler non-subscribing observable
getProjects() {
  this.projects$ = this.projectService.GetProjects().pipe(
    catchError(error => {
      // do something with error
      console.log(error);
      // then continue, nullifying
      return of(null);
    })
  )
}


RxJS 사용자 정의 연산자: 다시 던지기



이것은 있는 그대로 충분히 강력하지 않습니다. 포착된 오류가 반드시 예상대로 표시되는 것은 아닙니다. 대신, 우리는 debug operator 에 대해 했던 것처럼 catchError 에 대해서만 observable*에 대한 사용자 지정 연산자를 만들 것입니다. 이렇게 하면 사이트별로 예상되는 오류의 모양이 준비됩니다.

// custom RxJS operator
export const catchAppError = (message: string): MonoTypeOperatorFunction<any> => {
  return pipe(
    catchError(error => {
      // prepare error here, then rethrow, so that subscriber decides what to do with it
      const e = ErrorModelMap(error);
      return throwError(() => e);
    })
  );
};


이 연산자는 모든 응답 오류를 포착하기 위해 Http 인터셉터에서 연결될 수 있습니다.

// in our http interceptor
 return next
  .handle(adjustedReq)
  .pipe(
    // debug will take care of logging
    debug(`${req.method} ${req.urlWithParams}`, 'p'),
    // catch, will prepare the shape of error
    catchAppError(`${req.method} ${req.urlWithParams}`)
  )


오류 모델: 교정



UI의 오류 모델에는 최소한 다음이 포함될 수 있습니다.
  • 오류 코드: 올바른 UI 메시지를 얻기 위해 UI로 변환됨
  • 오류 메시지: 서버에서 오고, 상황에 맞지 않으며, 사용자에게는 매우 기술적이고 쓸모가 없지만 개발자에게는 유용함
  • 오류 상태: HTTP 응답이 있는 경우 유용할 수 있습니다.

  • // in error.model
    export interface IUiError {
        code: string;
        message?: string;
        status?: number;
    }
    

    catchError 연산자에서 해당 오류를 반환해야 하며 보내기 전에 매핑해야 합니다. 이를 위해서는 일반적으로 반사회적 API 개발자와 이야기해야 합니다. 형식은 개발자가 결정하기 때문입니다.

    다음과 같이 서버 오류가 발생한다고 가정합니다(웹에서 매우 일반적임).

    {
      "error": [
         {
           "message": "Database failure cyclic gibberish line 34-44 file.py",
           "code": "PROJECT_ADD_FAILED"
         }
       ]
    }
    

    UiError 매퍼는 다음과 같습니다. 카니발에 대비하세요.

    // add this the error.model file
    export const UiError = (error: any): IUiError => {
      let e: IUiError = {
        code: 'Unknown',
        message: error,
        status: 0,
      };
    
      if (error instanceof HttpErrorResponse) {
        // map general error
        e.message = error.message || '';
        e.status = error.status || 0;
    
        // dig out the message if found
        if (error.error?.errors?.length) {
          // accumulate all errors
          const errors = error.error.errors;
          e.message = errors.map((l: any) => l.message).join('. ');
          // code of first error is enough for ui
          e.code = errors[0].code || 'Unknown';
        }
      }
      return e;
    };
    

    RxJS 운영자는 이제 이 매퍼를 사용할 수 있습니다.

    // custom operator
    export const catchAppError = (message: string): MonoTypeOperatorFunction<any> => {
        return pipe(
            catchError(error => {
                // map first
                const  e = UiError(error);
               // then rethrow
                return throwError(() => e);
            })
        );
    };
    


    Unfortunately, you will not find two public APIs that return the same error format. Thus, you need to create a manual mapper for every specific type, individually. Will not go into details about this.



    디버그 맞춤 연산자를 만들려는 이전 시도에서도 오류를 로그아웃했습니다. 하지만 이제 새 연산자가 있으므로 디버그 연산자에서 로거를 제거하고 새 연산자에 배치하여 예상한 대로 정확하게 오류를 기록해야 합니다.

    // update debug operator, remove error handling
    export const debug = (message: string, type?: string): MonoTypeOperatorFunction<any> => {
        return pipe(
            tap({
                next: nextValue => {
                   // ...
                },
                // remove this part
                // error: (error) => {
                // ...
                // }
            })
        );
    };
    
    // custom operator, add debugging
    export const catchAppError = (message: string): MonoTypeOperatorFunction<any> => {
      return pipe(
        catchError((error) => {
          // map out to our model
          const e = UiError(error);
    
          // log
          _debug(e, message, 'e');
    
          // throw back to allow UI to handle it
          return throwError(() => e);
        })
      );
    };
    


    부품 처리



    지금까지 우리가 한 일은 서버에서 오류를 그대로 통과시키는 것뿐이었습니다. 이러한 오류를 처리하는 가장 일반적인 방법은 토스트 메시지입니다. 그러나 건배는 서사시입니다. 다음 주에 토스트에 대해 이야기하겠습니다. 😴

    여기까지 읽어주셔서 감사합니다. 불이 났다면 알려주세요.

    프로젝트가 진행 중입니다StackBlitz.

    자원


  • RxJs Error Handling: Complete Practical Guide
  • StackBlitz project

  • 관련 게시물


  • Writing a wrapper for console.log for better control in Angular, Part II
  • 좋은 웹페이지 즐겨찾기