Solución:
1 Se trata de la separación de preocupaciones en Angular
Uno de los principales beneficios de usar catchError
consiste en separar toda la lógica de recuperación de datos, incluidos todos los errores que pueden ocurrir en el camino, de la presentación de los datos.
1.1 Deje que los componentes solo se preocupen por la presentación de datos
Los componentes solo deben preocuparse por los datos (ya sea que estén allí o no). No deberían preocuparse por los detalles de cómo recuperar datos o todas las cosas que podrían salir mal durante la recuperación de datos.
Los componentes no deben buscar o guardar datos directamente y ciertamente no deben presentar datos falsos a sabiendas. Deben centrarse en presentar datos y delegar el acceso a los datos a un servicio.
[Angular Tutorial – Why Services]
Digamos que sus datos son una lista de elementos. Su componente llamaría un service.getItemList()
función y, como solo se preocupa por los datos, esperaría:
- una lista que contiene elementos
- una lista vacía
- sin lista, es decir
null
oundefined
Podrías manejar fácilmente todos estos casos con ngIf
en su plantilla de Componente y muestre los datos o algo más dependiendo del caso. Tener una función de servicio devuelve un limpio Observable que solo devuelve datos (o null
) y no se espera que arroje ningún error, mantiene el código en sus componentes delgado, ya que puede usar fácilmente AsyncPipe en una plantilla para suscribirse.
1.2 No permita que los componentes se preocupen por aspectos específicos de la recuperación de datos, como los errores
Su lógica de recuperación de datos y manejo de errores puede cambiar con el tiempo. Tal vez esté actualizando a una nueva Api y de repente tenga que manejar diferentes errores. No dejes que tus Componentes se preocupen por eso. Mueva esta lógica a un servicio.
Eliminar el acceso a los datos de los componentes significa que puede cambiar de opinión sobre la implementación en cualquier momento, sin tocar ningún componente. No saben cómo funciona el servicio. [Angular Tutorial – Get hero data]
1.3 Ponga la lógica de recuperación de datos y manejo de errores en un Servicio
El manejo de errores es parte de su lógica de recuperación de datos y no parte de su lógica de presentación de datos.
En su Servicio de recuperación de datos puede manejar el error en detalle con el catchError
operador. Tal vez haya algunas cosas que desee hacer con todos los errores, como:
- registrarlo
- mostrar un mensaje de error orientado al usuario como una notificación (ver Mostrar mensajes)
- buscar datos alternativos o devolver un valor predeterminado
Moviendo algo de esto en un this.handleError('getHeroes', [])
La función evita que tenga código duplicado.
Después de informar el error a la consola, el controlador crea un mensaje fácil de usar y devuelve un valor seguro a la aplicación para que pueda seguir funcionando. [Angular Tutorial – HTTP Error handling]
1.4 Facilitar el desarrollo futuro
Puede llegar un momento en el que necesite llamar a una función de servicio existente desde un nuevo componente. Tener su lógica de manejo de errores en una función de Servicio facilita esto, ya que no tendrá que preocuparse por el manejo de errores cuando llame a la función desde su nuevo Componente.
Por lo tanto, se trata de separar su lógica de recuperación de datos (en Servicios) de su lógica de presentación de datos (en Componentes) y la facilidad de extender su aplicación en el futuro.
2 Manteniendo vivos a los Observables
Otro caso de uso de catchError
es mantener vivos los Observables cuando estás construyendo Observables encadenados o combinados más complejos. Utilizando catchError
en los Observables internos le permite recuperarse de errores y mantener el Observable externo en funcionamiento. Esto no es posible cuando está utilizando el controlador de errores de suscripción.
2.1 Encadenamiento de múltiples observables
Mira esto longLivedObservable$
:
// will never terminate / error
const longLivedObservable$ = fromEvent(button, 'click').pipe(
switchMap(event => this.getHeroes())
);
longLivedObservable$.subscribe(console.log);
getHeroes(): Observable<Hero[]> {
return this.http.get<Hero[]>(this.heroesUrl).pipe(
catchError(error => of([]))
);
}
los longLivedObservable$
ejecutará una solicitud http cada vez que se haga clic en un botón. Nunca terminará, ni siquiera cuando la solicitud http interna arroje un error como en este caso catchError
devuelve un Observable que no genera errores, pero en su lugar emite una matriz vacía.
Si agrega una devolución de llamada de error a longLivedObservable$.subscribe()
y eliminado catchError
en getHeroes
los longLivedObservable$
en su lugar, terminaría después de la primera solicitud http que arroja un error y nunca volverá a reaccionar a los clics en los botones después.
Excursus: importa a qué Observable agregue catchError
Tenga en cuenta que longLivedObservable$
terminará si te mueves catchError
desde el interior Observable en getHeroes
al exterior Observable.
// will terminate when getHeroes errors
const longLivedObservable = fromEvent(button, 'click').pipe(
switchMap(event => this.getHeroes()),
catchError(error => of([]))
);
longLivedObservable.subscribe(console.log);
getHeroes(): Observable<Hero[]> {
return this.http.get<Hero[]>(this.heroesUrl);
}
Las notificaciones de “Error” y “Completo” pueden ocurrir solo una vez durante la Ejecución observable, y solo puede haber una de ellas.
En una ejecución observable, se pueden entregar notificaciones Next de cero a infinitas. Si se envía una notificación de Error o Completo, no se puede entregar nada más después.
[RxJS Documentation – Observable]
Los observables terminan cuando se envía una notificación de Error (o Completo). No pueden emitir nada más después. Utilizando catchError
en un Observable no cambia esto. catchError
no permite que su fuente Observable siga emitiendo después de que ocurra un error, solo le permite cambiar a un Observable diferente cuando ocurre un error. Este cambio solo ocurre una vez, ya que solo se puede enviar una notificación de error.
En el ejemplo anterior, cuando this.getHeroes()
errores esta notificación de error se propaga a la transmisión externa que lleva a una cancelación de suscripción de fromEvent(button, 'click')
y catchError
cambiar a of([])
.
Colocación catchError
en el Observable interno no expone la notificación de error al flujo externo. Entonces, si desea mantener vivo el Observable externo, debe manejar los errores con catchError
en el interior Observable, es decir, directamente donde ocurren.
2.2 Combinando múltiples Observables
Cuando está combinando Observables, por ejemplo, usando forkJoin
o combineLatest
es posible que desee que el Observable externo continúe si hay algún error Observable interno.
const animals$ = forkJoin(
this.getMonkeys(),
this.getGiraffes(),
this.getElefants()
);
animals$.subscribe(console.log);
getMonkeys(): Observable<Monkey[]> {
return this.http.get<Monkey[]>(this.monkeyUrl).pipe(catchError(error => of(null)));
}
getGiraffes(): Observable<Giraffe[]> {
return this.http.get<Giraffe[]>(this.giraffeUrl).pipe(catchError(error => of(null)));
}
getElefants(): Observable<Elefant[]> {
return this.http.get<Elefant[]>(this.elefantUrl).pipe(catchError(error => of(null)));
}
animals$
emitirá una matriz que contiene las matrices de animales que podría buscar o null
donde la búsqueda de animales falló. p.ej
[ [ Gorilla, Chimpanzee, Bonobo ], null, [ Asian Elefant, African Elefant ] ]
Aquí catchError
permite el animals$
Observable para completar y emitir algo.
Si quitaras catchError
de todas las funciones de recuperación y, en su lugar, agregó una devolución de llamada de error a animals$.subscribe()
luego animals$
Error si alguno de los Observables internos comete errores y, por lo tanto, no emitiría nada, incluso si algunos Observables internos se completaron con éxito.
Para obtener más información, lea: Manejo de errores de RxJs: Guía práctica completa