Как я могу сделать ошибку для объекта поведения и продолжить поток?

На одном конце у меня есть поток, который может иногда вызывать ошибку:

this.behaviorSubject.error(error)

Позже, однако, я хочу продолжить поток:

this.behaviorSubject.next(anotherValue)

на другом конце, у меня есть подписчик, подписанный на behaviorSubject.asObservable().

В подписке я обрабатываю значение и ошибку:

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

Я хочу, чтобы эффект был таким же, как простой обратный вызов onSuccess и onError, где onError вызывается каждый раз при возникновении ошибки и не препятствует будущим вызовам onSuccess. Как это сделать с помощью RXJS?

Я искал уловку, но, похоже, это просто предотвращает вызов вызываемых абонентов.

Ответ 1

Короткий ответ: Это невозможно.

Как работать с этим: Основная концепция RxJS заключается в том, что любой error или complete -call будет в основном "убивать" поток. Эта концепция заставляет вас не "просто бросать ошибки здесь и там, как вам будет угодно", но правильно обрабатывать ошибки и поток данных в вашем приложении. A BehaviorSubject, например, обычно предназначен для хранения данных, однако он должен не использоваться, чтобы также включать в себя процесс извлечения/создания этих данных и обработки возможных ошибок, которые могут возникать при извлечении данных.

Итак, если вы хотите пойти по книге, вы должны разделить поток на две части:

  • Извлечение/создание данных: поток, который будет запускаться один раз, а затем завершает и/или генерирует ошибку всякий раз, когда это происходит. Когда данные будут извлечены, они будут отправлены в хранилище.
  • Магазин (например, как в вашем случае: куча BehaviorSubjects): в хранилище поступают только достоверные данные, это означает, что здесь не обрабатывается ошибка, а все части, полагающиеся на хранилище может доверять хранилищу, что он содержит правильные данные.

В качестве примера ваш поток данных может выглядеть следующим образом (как примерный эскиз):

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

"Но это выглядит как много накладных расходов". Правда, однако, это обеспечивает чистый и понятный поток данных в вашем приложении, который легко тестировать и поддерживать. Также есть готовые к использованию хранилища-концепции, такие как ngrx или redux, который можно использовать.

Ответ 2

Rx основывается на концепции, что наблюдаемый либо активен, либо завершен (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')
  )

Обратите внимание, что этот пример завершается после трех излучений вместо 5

Когда вы вызываете this.behaviorSubject.error(error), он внутренне завершает наблюдение, содержащееся в вашей теме. Если вы хотите как-то исправить ошибки, вам нужно сделать свои ошибки значениями без ошибок:

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

Затем вы можете отличить на основе свойств исходящего значения, какое действие вы должны предпринять.

Ответ 3

Это может не сработать для вашей ситуации, но я столкнулся с этой проблемой при работе с Angular 2, потому что мы будем перемещаться по экранам и хотели бы, чтобы служба повторила API, а не просто вызывала функцию ошибки снова. Это фактически вызовет большие проблемы, потому что функция была вызвана в нашем конструкторе, и функция ошибки попытается обновить пользовательский интерфейс, который еще не был готов.

То, что я сделал, кажется, работает нормально и будет довольно чистым. Я создал объект reset Subject в обработчике ошибок.

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

Это работает в нашем случае, потому что каждый раз, когда вы переходите к экрану, новые подписки срываются с старого экрана, а затем настраиваются в новом, поэтому новый объект ничего не повредит.