Меню ×
Angular Interceptor - перехватчик ошибок http

Angular Interceptor - перехватчик ошибок http

Чистое и лаконичное управление ошибками http.

Angular Interceptor - перехватчик ошибок http

Время от времени в любом приложении могут возникать ошибки, необходимо внедрить подходящую систему обработки ошибок. В этой статье мы расскажем про различные типы http ошибок и то, как мы можем их решить.

Типы ошибок http

Есть много причин по которым HTTP-запросы не выполняются. Плохое сетевое соединение, неправильный URL-адрес или запрошенный сервер в настоящее время недоступен, и это лишь некоторые из них. В целом ошибки можно разделить на два типа: ошибки на стороне сервера и ошибки на стороне клиента.

С обоими типами нужно обращаться по-разному, поскольку мы, клиенты, несем ответственность за последние, мы можем обращаться с ними иначе, чем с первыми. В случае ошибок на стороне сервера мы можем передать только соответствующее сообщение об ошибке, полученное от сервера или попытаться повторить попытку, поскольку сервер мог быть временно недоступен.

Однако на стороне клиента мы могли использовать неверный URL-адрес или отсутствующий параметр и нам нужно это исправить.

В обоих случаях важно предоставлять пользователю сообщения об ошибках, для которых также подходит глобальная обработка ошибок.

HTTP Interceptor

С помощью так называемого HttpInterceptor, Angular предоставляет нам способ преобразовывать http-запросы и ответы.

Angular HTTP Interceptor

Через интерфейс мы можем реализовать intercept метод, который вызывается автоматически при каждом HTTP-запросе, сделанном через наше приложение. Это позволяет нам реализовать централизованную обработку ошибок с помощью перехватчика.

Перехватчик имеет следующие параметры:

  • req — объект исходящего запроса для обработки.
  • next — следующий перехватчик в цепочке или серверная часть, если в цепочке не осталось перехватчиков.
  • Returns: наблюдаемая часть потока событий.

Давайте рассмотрим пример.

Обработчик HTTP-ошибок

Мы можем создать новый перехватчик с помощью Angular CLI со следующей командой:

	
ng generate interceptor http-error
	

Обычно мы держим свои перехватчики в CoreModule, который содержит глобальные функции и загружается сразу после запуска приложения. Перехватчик должен быть зарегистрирован в модуле. Мы можем сделать это, добавив его в массив провайдеров:

	
@NgModule({
  imports: [CommonModule, HttpClientModule],
  declarations: [,
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: HttpErrorInterceptor,
      multi: true,
    },
  ],
})
export class CoreModule {}
	

Свойство multi должно быть установлено на true, т.к. может быть несколько перехватчиков, использующих HTTP_INTERCEPTORS токен внедрения.

Затем мы реализуем intercept метод, в котором мы добавляем catchError оператор RxJS к запросу, который будет использоваться для нашей логики обработки ошибок.

	
@Injectable()
export class HttpErrorInterceptor implements HttpInterceptor {

  intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    return next.handle(request).pipe(
      catchError((err) => {
        if (err instanceof ErrorEvent) {
          console.log('this is an error in the code');
        } else {
          console.log('this is an error return by the server');
        }
        return throwError(err);
      }),
    );
  }
}
	

Внутри catchError мы проверяем, является ли ошибка экземпляром ErrorEvent, и в этом случае тип неизвестен, и вероятно, что-то не так с нашим кодом. В противном случае мы можем проверить ответ сервера и соответствующим образом обработать его.

Как уже упоминалось, важно отображать осмысленное сообщение для пользователя независимо от типа ошибки. Для этой цели мы можем определить интерфейс, чтобы он имел общую структуру для всех сообщений об ошибках, например, как показано ниже:

	
export enum ErrorSeverity {
  INFO = 'INFO',
  WARNING = 'WARNING',
  ERROR = 'ERROR',
  FATAL = 'FATAL',
}

export interface BackendError {
  title?: string;
  message: string;
  severity: ErrorSeverity;
  code: string;
}
	

У нас есть необязательный заголовок, который мы можем использовать, например, для отображения всплывающего сообщения. Затем само сообщение об ошибке, различные уровни важности и код ответа http. Теперь добавим интерфейс к нашему перехватчику:

	
@Injectable()
export class HttpErrorInterceptor implements HttpInterceptor {
  intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    return next.handle(request).pipe(
      catchError((err) => {
        let error: BackendError;
        if (err instanceof ErrorEvent) {
          // this is client side error
          error = this.handleUnknownError();
        } else {
          // this is server side error
          error = this.handleBackendError(error, err);
        }
        return throwError(() => error);
      })
    );
  }

  private handleUnknownError(): BackendError {
    // this is not from backend. Format our own message.
    return {
      message: 'Unknown error!',
      severity: ErrorSeverity.FATAL,
      code: 'UNKNOWN_ERROR',
    };
  }

  private handleBackendError(error: BackendError, err): BackendError {
    // Backend returned error, format it here
    return {
      title: err.error?.title || 'Default title',
      message: err.error && err.error.message ? err.error.message : err.error ? err.error : err.message,
      severity: ErrorSeverity.ERROR,
      code: err.error?.identifierCode ? err.error.identifierCode : 'BACKEND_ERROR',
    };
  }
}
	

Мы преобразуем ошибку для обоих типов, а затем повторно выдаем ошибку, чтобы она могла быть обработана соответствующей службой, из которой изначально был инициирован запрос.

Затем мы можем интегрировать логику повторных попыток для определенных кодов ответов, упомянутых выше. Повтор для http-запросов можно реализовать достаточно просто с помощью RxJS. Для этого есть два оператора: retry и retryWhen.

Любой из них можно использовать для повторения наблюдаемой последовательности заданное количество раз. В данном случае retryWhen больше подходит оператор, так как мы можем работать с произвольными условиями, а не только с количеством повторных попыток.

Для нашей стратегии повторных попыток мы можем создать собственный оператор:

	
export const genericRetryStrategy =
  ({
    maxRetryAttempts = 3,
    scalingDuration = 1000,
    excludedStatusCodes = [],
  }: {
    maxRetryAttempts?: number;
    scalingDuration?: number;
    excludedStatusCodes?: [];
  } = {}) =>
  (attempts: Observable<any>) => {
    return attempts.pipe(
      mergeMap((error, i) => {
        const retryAttempt = i + 1;
        if (retryAttempt > maxRetryAttempts || excludedStatusCodes.find((e) => e === error.status)) {
          return throwError(error);
        }
        return timer(retryAttempt * scalingDuration);
      }),
    );
  };
	

Настраиваемая повторная попытка с увеличенной продолжительностью.

Мы можем настроить наш оператор, используя три параметра. Во-первых с количеством попыток, во-вторых мы можем использовать scalingDuration для создания задержки между попытками и в-третьих любые http-коды можно исключить, например 404, т.к. повторная попытка, например, для «404 — не найдено» не имеет смысла.

Новый оператор теперь можно просто вставить в pipe нашего перехватчика вместе с retryWhen:

	
@Injectable()
export class HttpErrorInterceptor implements HttpInterceptor {
  intercept(
    request: HttpRequest<unknown>,
    next: HttpHandler
  ): Observable<HttpEvent<unknown>> {
    return next.handle(request).pipe(
      retryWhen(genericRetryStrategy({ excludedStatusCodes: [404, ...] })),
      catchError((err) => {
        ...
    );
  }
}
	

Похожие материалы

Не часто при разработке SPA приложения появляется задача с динамическим добавлением JS файла на страницу. В данной статье рассмотрим несколько возможных способов динамического добавления script в Angular.

Angilar создание обычных и вложенных компонентов через CLI. Описание модели.

В данном руководстве пойдет речь о селекторе, Databinding, интерполяции, связывание свойств и многом другом.

наверх