¿Cómo atrapar la excepción correctamente desde http.request ()?

132

Parte de mi código:

import {Injectable} from 'angular2/core';
import {Http, Headers, Request, Response} from 'angular2/http';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/map';

@Injectable()
export class myClass {

  constructor(protected http: Http) {}

  public myMethod() {
    let request = new Request({
      method: "GET",
      url: "http://my_url"
    });

    return this.http.request(request)
      .map(res => res.json())
      .catch(this.handleError); // Trouble line. 
                                // Without this line code works perfectly.
  }

  public handleError(error: Response) {
    console.error(error);
    return Observable.throw(error.json().error || 'Server error');
  }

}

myMethod() produce una excepción en la consola del navegador:

EXCEPCIÓN ORIGINAL: TypeError: this.http.request (...). Map (...). Catch no es una función

mnv
fuente

Respuestas:

213

Quizás pueda intentar agregar esto en sus importaciones:

import 'rxjs/add/operator/catch';

También puedes hacer:

return this.http.request(request)
  .map(res => res.json())
  .subscribe(
    data => console.log(data),
    err => console.log(err),
    () => console.log('yay')
  );

Por comentarios:

EXCEPCIÓN: TypeError: Observable_1.Observable.throw no es una función

Del mismo modo, para eso, puede usar:

import 'rxjs/add/observable/throw';
acdcjunior
fuente
2
Gracias por la ayuda, funciona. Después de eso tengo el mismo problema con la throw()función. Agregué esta línea en su import 'rxjs/Rx';lugar. Ahora todos los operadores funcionan correctamente.
mnv
¿Simulaste un error para ver si .catchrealmente funciona? Eso .subscribe() funciona seguro.
acdcjunior
1
Sí, el segundo problema fue EXCEPTION: TypeError: Observable_1.Observable.throw is not a function. Puede corregirse con la respuesta @MattScarpino o en este caso desde el plunker como dije anteriormente: angular.io/resources/live-examples/server-communication/ts/…
mnv
16
Solo importa el lanzamiento también: import 'rxjs/add/observable/throw';y no importes todo, es demasiado grande.
dfsq
Gran solución, muy útil, podría agregar que el (err) es de tipo Respuesta
Mohammed Suez
77

Nuevo servicio actualizado para usar HttpClientModule y RxJS v5.5.x :

import { Injectable }                    from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Observable }                    from 'rxjs/Observable';
import { catchError, tap }               from 'rxjs/operators';
import { SomeClassOrInterface}           from './interfaces';
import 'rxjs/add/observable/throw';

@Injectable() 
export class MyService {
    url = 'http://my_url';
    constructor(private _http:HttpClient) {}
    private handleError(operation: String) {
        return (err: any) => {
            let errMsg = `error in ${operation}() retrieving ${this.url}`;
            console.log(`${errMsg}:`, err)
            if(err instanceof HttpErrorResponse) {
                // you could extract more info about the error if you want, e.g.:
                console.log(`status: ${err.status}, ${err.statusText}`);
                // errMsg = ...
            }
            return Observable.throw(errMsg);
        }
    }
    // public API
    public getData() : Observable<SomeClassOrInterface> {
        // HttpClient.get() returns the body of the response as an untyped JSON object.
        // We specify the type as SomeClassOrInterfaceto get a typed result.
        return this._http.get<SomeClassOrInterface>(this.url)
            .pipe(
                tap(data => console.log('server data:', data)), 
                catchError(this.handleError('getData'))
            );
    }

Servicio anterior, que utiliza el HttpModule en desuso:

import {Injectable}              from 'angular2/core';
import {Http, Response, Request} from 'angular2/http';
import {Observable}              from 'rxjs/Observable';
import 'rxjs/add/observable/throw';
//import 'rxjs/Rx';  // use this line if you want to be lazy, otherwise:
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/do';  // debug
import 'rxjs/add/operator/catch';

@Injectable()
export class MyService {
    constructor(private _http:Http) {}
    private _serverError(err: any) {
        console.log('sever error:', err);  // debug
        if(err instanceof Response) {
          return Observable.throw(err.json().error || 'backend server error');
          // if you're using lite-server, use the following line
          // instead of the line above:
          //return Observable.throw(err.text() || 'backend server error');
        }
        return Observable.throw(err || 'backend server error');
    }
    private _request = new Request({
        method: "GET",
        // change url to "./data/data.junk" to generate an error
        url: "./data/data.json"
    });
    // public API
    public getData() {
        return this._http.request(this._request)
          // modify file data.json to contain invalid JSON to have .json() raise an error
          .map(res => res.json())  // could raise an error if invalid JSON
          .do(data => console.log('server data:', data))  // debug
          .catch(this._serverError);
    }
}

Yo uso .do()( ahora.tap() ) para la depuración.

Cuando hay un error de servidor, el bodydel Responseobjeto consigo desde el servidor que estoy usando (lite-servidor) contiene sólo texto, de ahí la razón por la que utilizo err.text()anteriormente en lugar de err.json().error. Es posible que deba ajustar esa línea para su servidor.

Si res.json()genera un error porque no pudo analizar los datos JSON, _serverErrorno obtendrá un Responseobjeto, de ahí el motivo de la instanceofverificación.

En esto plunker, cambie urla ./data/data.junkpara generar un error.


Los usuarios de cualquiera de los servicios deben tener un código que pueda manejar el error:

@Component({
    selector: 'my-app',
    template: '<div>{{data}}</div> 
       <div>{{errorMsg}}</div>`
})
export class AppComponent {
    errorMsg: string;
    constructor(private _myService: MyService ) {}
    ngOnInit() {
        this._myService.getData()
            .subscribe(
                data => this.data = data,
                err  => this.errorMsg = <any>err
            );
    }
}
Mark Rajcok
fuente
4

Hay varias formas de hacer esto. Ambos son muy simples. Cada uno de los ejemplos funciona muy bien. Puede copiarlo en su proyecto y probarlo.

El primer método es preferible, el segundo está un poco desactualizado, pero hasta ahora también funciona.

1) Solución 1

// File - app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';

import { AppComponent } from './app.component';
import { ProductService } from './product.service';
import { ProductModule } from './product.module';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule
  ],
  providers: [ProductService, ProductModule],
  bootstrap: [AppComponent]
})
export class AppModule { }



// File - product.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

// Importing rxjs
import 'rxjs/Rx';
import { Observable } from 'rxjs/Rx';
import { catchError, tap } from 'rxjs/operators'; // Important! Be sure to connect operators

// There may be your any object. For example, we will have a product object
import { ProductModule } from './product.module';

@Injectable()
export class ProductService{
    // Initialize the properties.
    constructor(private http: HttpClient, private product: ProductModule){}

    // If there are no errors, then the object will be returned with the product data.
    // And if there are errors, we will get into catchError and catch them.
    getProducts(): Observable<ProductModule[]>{
        const url = 'YOUR URL HERE';
        return this.http.get<ProductModule[]>(url).pipe(
            tap((data: any) => {
                console.log(data);
            }),
            catchError((err) => {
                throw 'Error in source. Details: ' + err; // Use console.log(err) for detail
            })
        );
    }
}

2) Solución 2. Es antigua, pero aún funciona.

// File - app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpModule } from '@angular/http';

import { AppComponent } from './app.component';
import { ProductService } from './product.service';
import { ProductModule } from './product.module';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpModule
  ],
  providers: [ProductService, ProductModule],
  bootstrap: [AppComponent]
})
export class AppModule { }



// File - product.service.ts
import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';

// Importing rxjs
import 'rxjs/Rx';
import { Observable } from 'rxjs/Rx';

@Injectable()
export class ProductService{
    // Initialize the properties.
    constructor(private http: Http){}

    // If there are no errors, then the object will be returned with the product data.
    // And if there are errors, we will to into catch section and catch error.
    getProducts(){
        const url = '';
        return this.http.get(url).map(
            (response: Response) => {
                const data = response.json();
                console.log(data);
                return data;
            }
        ).catch(
            (error: Response) => {
                console.log(error);
                return Observable.throw(error);
            }
        );
    }
}
Victor Isaikin
fuente
-1

Las funciones RxJS deben importarse específicamente. Una manera fácil de hacer esto es importar todas sus características conimport * as Rx from "rxjs/Rx"

Luego, asegúrese de acceder a la Observableclase como Rx.Observable.

MatthewScarpino
fuente
15
Rxjs es un archivo muy grande, si importas todas sus características, aumentará tu tiempo de carga
Soumya Gangamwar
No debe importar todo desde Rxjs si solo necesita uno o dos operadores.
marcel-k
-4

en la última versión de uso angular4

import { Observable } from 'rxjs/Rx'

importará todas las cosas requeridas.

Munish Sharma
fuente
20
No hagas esto, importará todos los Rxjs.
marcel-k
¡Y así se incrementará el tamaño del paquete!
Tushar Walzade