首页 > 解决方案 > 为什么使用 catchError 而不是在 Angular 的订阅错误回调中处理错误

问题描述

所以我通常会这样写我的http请求

服务

getData() {
  return this.http.get('url')
}

零件

getTheData() {
  this.service.getData().subscribe(
    (res) => {
      //Do something
    }, 
    (err) => {
      console.log('getData has thrown and error of', err)
    })

但是查看 Angular 文档,他们似乎在 Service 中将其格式化为这样

getHeroes(): Observable<Hero[]> {
  return this.http.get<Hero[]>(this.heroesUrl)
    .pipe(
      catchError(this.handleError('getHeroes', []))
    );
}

这有什么隐含的好处,因为它对我来说似乎很冗长,而且我个人从来没有需要管道我的错误。

标签: angularrxjsangular2-observables

解决方案


1 这都是关于 Angular 中的关注点分离

使用的一个主要好处catchError是将整个数据检索逻辑(包括在此过程中可能发生的所有错误)与数据的表示分开。

1.1 让组件只关心数据的呈现

组件应该只关心数据(无论它是否存在)。他们不应该关心如何检索数据的细节或在数据检索过程中可能出错的所有事情。

组件不应该直接获取或保存数据,当然也不应该故意提供虚假数据。他们应该专注于呈现数据并将数据访问委托给服务。
[Angular 教程 - 为什么选择服务]

假设您的数据是项目列表。您的组件会调用一个service.getItemList()函数,并且由于它只关心数据,因此会期望:

  • 包含项目的列表
  • 一个空列表
  • 没有清单,即nullundefined

ngIf您可以在组件模板中轻松处理所有这些情况,并根据情况显示数据或其他内容。让 Service 函数返回一个干净的Observable ,它只返回数据(或null)并且不会引发任何错误,这样可以使组件中的代码保持精简,因为您可以轻松地在模板中使用AsyncPipe进行订阅。

1.2 不要让组件关心诸如错误之类的数据检索细节

您的数据检索和错误处理逻辑可能会随着时间而改变。也许您正在升级到新的 Api 并且突然不得不处理不同的错误。不要让您的组件担心这一点。将此逻辑移至服务。

从组件中删除数据访问意味着您可以随时改变对实施的想法,而无需触及任何组件。他们不知道服务是如何运作的。[Angular 教程 - 获取英雄数据]

1.3 将数据检索和错误处理逻辑放在一个Service中

处理错误是数据检索逻辑的一部分,而不是数据表示逻辑的一部分。

在您的数据检索服务中,您可以与操作员详细处理错误catchError。也许您想对所有错误执行一些操作,例如:

  • 记录下来
  • 将面向用户的错误消息显示为通知(请参阅显示消息
  • 获取替代数据或返回默认值

将其中的一部分移到this.handleError('getHeroes', [])函数中可以避免重复代码。

将错误报告给控制台后,处理程序构造一条用户友好的消息并向应用程序返回一个安全值,以便它可以继续工作。[Angular 教程 - HTTP 错误处理]

1.4 让未来的发展更容易

有时您可能需要从新组件调用现有服务功能。在 Service 函数中包含错误处理逻辑使这很容易,因为在从新组件调用函数时不必担心错误处理。

因此,它归结为将数据检索逻辑(在服务中)与数据表示逻辑(在组件​​中)分离,以及将来扩展应用程序的便利性。

2 保持 Observables 活着

的另一个用例catchError是在构建更复杂的链式或组合式 Observable 时保持 Observable 的活动。在内部 Observable 上使用catchError可以让您从错误中恢复并保持外部 Observable 运行。当您使用订阅错误处理程序时,这是不可能的。

2.1 链接多个 Observable

看看这个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([]))
  );
}

longLivedObservable$每当单击按钮时,它将执行一个 http 请求。即使内部 http 请求抛出错误,它也永远不会终止,因为在这种情况下catchError,它会返回一个没有错误但会发出一个空数组的 Observable。

如果您将错误回调添加到并在其中longLivedObservable$.subscribe()删除catchError,则会在第一个引发错误的 http 请求之后终止,并且之后不再对按钮单击做出反应。getHeroeslongLivedObservable$


Excursus:添加哪个 Observable 很重要catchError

请注意,如果您从内部 Observable移动到外部 Observable ,longLivedObservable$ 它将终止。catchErrorgetHeroes

// 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);
}

在 Observable Execution 期间,“错误”和“完成”通知可能只发生一次,并且只能有其中之一。

在 Observable Execution 中,可能会传递零到无限的 Next 通知。如果发送了错误或完成通知,则之后无法发送任何其他通知。
[RxJS 文档 - 可观察的]

当一个错误(或完成)通知被传递时,Observables 终止。之后他们不能发射任何其他东西。在 Observable 上使用catchError不会改变这一点。catchError不允许您的源 Observable 在发生错误后继续发射,它只允许您在发生错误时切换到不同的 Observable。此切换仅发生一次,因为只能发送一个错误通知。

在上面的示例中,当出现错误时,this.getHeroes()此错误通知会传播到外部流,从而导致取消订阅fromEvent(button, 'click')catchError切换到of([]).

放置catchError在内部 Observable 上不会将错误通知暴露给外部流。所以如果你想保持外部 Observable 活着,你必须处理catchError内部 Observable 上的错误,即直接在它们发生的地方。


2.2 组合多个 Observable

当您组合 Observables 时,例如使用forkJoin或者combineLatest如果有任何内部 Observable 错误,您可能希望外部 Observable 继续。

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$将发出一个数组,其中包含它可以获取的动物数组或null获取动物失败的地方。例如

[ [ Gorilla, Chimpanzee, Bonobo ], null, [ Asian Elefant, African Elefant ] ]

这里catchError允许animals$Observable 完成并发出一些东西。

如果您catchError要从所有 fetch 函数中删除,而是添加一个错误回调,animals$.subscribe()那么animals$如果任何内部 Observables 错误,那么即使某些内部 Observables 成功完成,也不会发出任何内容。

要了解更多信息,请阅读:RxJs 错误处理:完整实用指南


推荐阅读