如何在行为主题上抛出错误并继续流?

时间:2017-01-24 11:43:00

标签: javascript typescript rxjs

一方面,我有一个可能偶尔会抛出错误的流:

this.behaviorSubject.error(error)

然而,稍后,我想继续使用该流:

this.behaviorSubject.next(anotherValue)

另一方面,我有一个订阅者订阅了behaviorSubject.asObservable()

在订阅中,我正在处理值和错误:

.subscribe( 
   ( value ) =>  {  /* ok */ },
   ( error ) =>  {  /* some error */ }
);

我希望效果与简单的onSuccessonError回调相同,每次发生错误时都会调用onError并且不会阻止将来{{1}来自被叫的电话。我如何使用RXJS做到这一点?

我已经研究了捕获,但它似乎只是防止错误被订阅者调用。

4 个答案:

答案 0 :(得分:18)

简答:这是不可能的。

如何使用此功能:RxJS的基本概念是任何errorcomplete - 调用基本上“kill” a流。这个概念迫使你“只是随心所欲地抛出错误”,但要正确处理错误和应用程序中的数据流。例如,BehaviorSubject通常用于保存数据,但是还应包括检索/创建该数据的过程,并处理在检索期间可能发生的错误数据。

因此,如果你想按照本书,你应该将你的流程分成两部分

  1. 检索/创建数据:一个流,它将运行一次然后完成和/或每当发生错误时抛出错误。 检索数据后,它将被发送到商店。
  2. 商店(例如,在您的情况下:一堆BehaviorSubjects):只有有效数据到达商店,这意味着此处没有进行错误处理,所有部件都依赖于商店可以信任它拥有正确数据的商店。
  3. 作为示例,您的数据流可能如下所示(粗略草图):

    <强> store.ts

    dataStore: BehaviorSubject<IData> = new BehaviorSubject<IData>();
    errorMessage: BehaviorSubject<IErrorMsg> = new BehaviorSubject<IErrorMsg>();
    

    数据-retrieval.ts

    fetchDataById(id: string) {
        httpService.get(`some/rest/endpoint/${id}`)
            .subscribe(handleData, handleError);
    }
    
    handleData(data: IData) {
        errorMessage.next(null);
        dataStore.next(data);
    }
    
    handleError(error: Error) {
        errorMessage.next(error.message);
        dataStore.next(null);
    }
    

    “但这看起来像很多开销......” - 没错,但是它可以确保应用程序内的数据流清晰易懂,易于测试和保持。此外,还有可以使用的即用型商店概念,例如ngrxredux

答案 1 :(得分:4)

Rx基本上建立在observable活动或最终化(onComplete或onError)的概念之上。当Observable最终确定时,它将取消订阅其上游的Observable。没有.catch可以修复该行为,它只为您提供了将错误映射到其他内容的选项。

Rx.Observable.interval(500)
  .mergeMap(i => i % 3 == 2 ? Rx.Observable.throw(new Error('kboom')) : Rx.Observable.of(i))
  .catch(err => Rx.Observable.of(err.message))
  .subscribe(
    val => console.log('val: ' + val),
    err => console.log('err: ' + err),
    () => console.log('stream completed')
  )

请注意,此示例在3次排放后完成,而不是5次

当您调用this.behaviorSubject.error(error)时,它将在内部完成主题中包含的Observable。如果你想以某种方式发出错误,那么你需要使你的错误成为非错误值:

this.behaviorSubject.next({ value: 'somevalue' });
this.behaviorSubject.next({ error: error });
this.behaviorSubject.next({ value: 'somevalue' });

然后,您可以根据发射值的属性区分您应该采取的行动。

答案 2 :(得分:0)

这可能不适用于您的情况,但在使用Angular 2时遇到了同样的问题,因为我们会在屏幕之间导航,并希望服务重试API,而不是再次调用错误函数。它实际上会导致更大的问题,因为在我们的构造函数中调用了函数,错误函数会尝试更新尚未准备好的UI。

我所做的似乎工作得很好而且非常干净。我在错误处理程序中创建了一个重置​​主题。

subject.subscribe( 
   ( value ) =>  {  /* ok */ },
   ( error ) =>  {  
      //handle error
      //reset subject
      this.subject = new Subject<any>();
   }
);

这适用于我们的情况,因为每次您导航到屏幕时,新订阅都会从旧屏幕中拆除,然后在新屏幕中设置,因此新主题不会对任何事情造成伤害。

答案 3 :(得分:0)

正如其他人所说,错误结果预计是终端。 我个人认为(在你的情况下)有 3 种类型的结果(技术上是 4) 理想的结果是调用 next() 的成功案例。
致命失败(内存不足错误或任何其他形式的“调用无法继续”错误)应该调用 error()。 第三种形式也是解决问题的关键是非终端错误。这是“结果不成功”的形式。因为您的意思是继续,所以它不是 rxjs 意义上的错误。这只是另一种类型的结果。结果表明发生了其他事情。 (第 4 种形式是“处理完成”:尽我所能,并且没有错误地退出)

现在我不确定细节,但我记得打字稿可以处理联合类型(如果不是,你可能不得不使用“any”的结果类型)。 使用 Unions,您可以将对象声明为(例如) Subject 这里的重点是您可以从下一个语句发送不同的结果。 您会执行以下操作...

DoCoolThingFunction():Subject<string|failtype>
{
    const response = new Subject<string|failtype>();
    deeperPeriodicAsyncOperation.subscribe((result) => {

      if (result is something I like) {
        response.next(result.toString());

      } else if (result is final value) {
        response.next(result.toString());
        response.complete();

      } else if (result is something teminal) {
        response.error(result);

      } else if (result is non-terminal error) {
        response.next(new failtype(result));
      }
    });
    return response;
}

基本上,这是说“此范围内的错误是非终止性的。因此它不是错误,它只是一种不同类型的操作数据”。

当然,由您的接收代码来确定它所传递的结果类型。我不知道是否有任何巧妙的方法可以做到这一点。如果结果处理程序可以有多个不同类型的响应 ((result:string) =>{}, (result:failtype)=>{}) 等,那就太好了,但这并不是该线程的真正主题。