Pesquisa de site

typescript - Manipulação centralizada de erros HTTP no Angular 4


Quero informar ao usuário se uma requisição HTTP falhar, sem precisar escrever código adicional para cada requisição HTTP.

Eu tinha um protótipo funcional para Angular 2:

@Injectable()
export class MyErrorHandler extends ErrorHandler {

  constructor() {
    super(false);
  }

  handleError(error: any) {
    this.tellUser(error);
    super.handleError(error);
  }

  tellUser(error: any) {
    // dig for the root cause
    if (error.rejection) {
      error = error.rejection;
    }
    if (error instanceof ViewWrappedError) {
       error = error.originalError;
    }

    let message;
    if (error instanceof Response) {
      // TODO: localize and display nicely
      const messages = {
        0: "Could not connect to server",
      }
      message = messages[error.status] || ("The server reported a system error (HTTP " + error.status + ")");
    } else {
      message = "A system error occurred.";
    }
    console.warn(message);
  }
}

mas o ViewWrappedError foi substituído no Angular 4 por

export function viewWrappedDebugError(err: any, context: DebugContext): Error {
  if (!(err instanceof Error)) {
    // errors that are not Error instances don't have a stack,
    // so it is ok to wrap them into a new Error object...
    err = new Error(err.toString());
  }
  _addDebugContext(err, context);
  return err;
}

O que, ao invocar toString no HttpResponse, torna difícil consultar o código de status ...

Eu esperaria que o Angular fornecesse uma API pública e bem suportada para o gerenciamento centralizado de erros. Realmente não há nenhuma maneira de lidar centralmente com erros HTTP além de analisar mensagens de erro?

Atualização: Eu preferiria se um componente pudesse facilmente substituir o tratamento centralizado de erros.

Resposta #1

Outra opção é simplesmente ter um serviço que delega ao serviço Http enquanto adiciona qualquer tratamento de erro personalizado, lógica de depuração, cabeçalhos extras etc. que você precisar para interagir com sua API.

Este serviço então se torna seu ponto centralizado para interagir com qualquer coisa relacionada a Http e, como tal, você pode centralizar o tratamento de erros bem ali. Em qualquer projeto que eu desenvolvo, EU SEMPRE crio tal classe porque praticamente toda API com a qual você interage tem alguma configuração comum que precisa acontecer com cada requisição (mesmo que essa configuração seja tão pequena quanto especificar a URL base a ser usada).

Exemplo de tal classe:

export class ApiGateway {

  baseURL = "https://myapi.com";  // or sometimes pulled from another file
  constructor(private http: Http) { }

  get(path, params) {
    showLoadingIndicator();

    let headers = this.createMySpecialHeaders();
    let options = { headers: headers } // and whatever else you need to generalize
    let fullUrl = this.baseUrl + path + '?' + this.urlEncode(params)`;
    return this.get(path, params, options)
               .do(() => hideLoadingIndicator())
               .map(res => res.json())
               .catch(err => {
                    hideLoadingIndicator();
                  // show error message or whatever the app does with API errors etc
                  // sometimes rethrow the error, depending on the use case
                })
  }
}

Para mim, estes são princípios básicos de POO - você envolve suas interações com coisas fora do seu controle em uma classe adaptadora para permitir alguma proteção contra mudanças externas e para alterar a API dessa coisa externa para algo que você prefira, se necessário.

Com uma classe dessas em vigor, se, por exemplo, você atualizar para o Angular 4 e os meios de receber erros mudarem, você só terá um lugar para alterar para lidar com a nova técnica de erro.

Resposta #2

Estou centralizando esse tipo de lógica dentro de um BaseService, e então herdo todos os serviços a partir dele. O Angular 2 não fornece Http Interceptors como a versão anterior, tornando esse tipo de coisa difícil de lidar.

import { Injectable } from '@angular/core';
import { Response } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import { NotificationService } from '********';


@Injectable()
export class BaseService {
  protected url: string;
  protected http: Http;

  constructor(http: Http, protected notification: NotificationService) {
    this.http = http;
  }

  protected extractData(res: Response) {
      //
      // Depende do formato da response, algumas APIs retornam um objeto data e dentro deste o conteudo propriamente dito
      // Em minhas APIs sempre retorno diretamente o objeto que desejo
      //
      return res.json();
  }

  protected handleError(error: Response | any) {
    let erros: string[] = [];

    switch (error.status) {
      case 400:
        let res = error.json();

    //
    // Depende do formato, minhas APIs sempre retornar um objeto com array errors quando tenho 400
    //
        for (let index = 0; index < res.errors.length; index++) {
          let msg = res.errors[index];
          erros.push(msg);
        };

        this.notification.showMultipleWarn(erros);
        break;
      case 404:
    this.notification.showWarning('O recurso requisitado não existe.');
    break;
      case 406:
      case 409:
      case 500:
        this.notification.showError('Ocorreu um erro inesperado.');
        break;
      default:
        this.notification.showError('Ocorreu um erro inesperado.');
        break;
    }

    return Observable.throw(new Error(error.status));
  }
}

Aqui está meu post no blog explicando uma maneira de lidar com isso de forma menos verbosa: Receitas Angular2: Interceptando erros HTTP de forma Global

O post está escrito em português, mas o código pode te dar algumas pistas. Estou usando essa abordagem em alguns projetos enormes agora, e está funcionando bem.

Artigos relacionados