меню

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

Чистое и лаконичное управление ошибками 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) => {
        ...
    );
  }
}
  

Возможно, вам будет интересно

Формы. Реактивный подход.

Работа с формами. Реактивный подход (Reactive).

Структурные директивы

Что такое структурные директивы. Встроенные и собственные структурные директивы.

ng-content

Использование тега ng-content и размещения верстки из шаблона в теге вызова компонента в Angular

Шаблон и стили компонента

Стилилизация компонента в Angular, способы определения стилей, подключени внешнего файла стилей, вынесение кода шаблона во внешний файл html

Оформление заявки

Документы на создание сайта

Изучите наше коммерческое предложение, заполните БРИФ и отправьте его на почту maxidebox@list.ru. Изучив все пожелания из БРИФ-а, обратным ответом оповестим Вас по стоимости разработке, ответим на вопросы.

КП на создание сайта Коммерческое предложение на созданеи сайта

Мы берем на себя ответственность за все стадии работы и полностью избавляем клиентов от забот и необходимости вникать в тонкости.

Скачать БРИФ (акета) на создание сайта Скачать БРИФ (акета) на создание сайта

Зополните у БРИФ-а все необходимые поля. Сделайте краткое описание к каждому из пунктов анкеты, привидите примеры в соответсвующий пунктах - это позволит лучше понять Ваши ожидания и требования к сайту