web-dev-qa-db-ja.com

エラーを無視して無限ストリームを継続する方法は?

例外を無視し、無限ストリーム(私の場合は場所のストリーム)を継続する方法を知りたいですか?

現在のユーザーの位置を取得し( Android-ReactiveLocation を使用)、それらをAPIに送信しています( Retrofit を使用)。

私の場合、ネットワーク呼び出し中に例外が発生すると(タイムアウトなど)、onErrorメソッドが呼び出され、ストリーム自体が停止します。それを避ける方法は?

アクティビティ:

private RestService mRestService;
private Subscription mSubscription;
private LocationRequest mLocationRequest = LocationRequest.create()
            .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
            .setInterval(100);
...
private void start() {
    mRestService = ...;
    ReactiveLocationProvider reactiveLocationProvider = new ReactiveLocationProvider(this);
    mSubscription = reactiveLocationProvider.getUpdatedLocation(mLocationRequest)
            .buffer(50)
            .flatMap(locations -> mRestService.postLocations(locations)) // can throw exception
            .subscribeOn(Schedulers.newThread())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe();
}

RestService:

public interface RestService {
    @POST("/.../")
    Observable<Response> postLocations(@Body List<Location> locations);
}
38
Ziem

mRestService.postLocations(locations)は1つのアイテムを発行し、完了します。エラーが発生すると、エラーが発生し、ストリームが完了します。

flatMapでこのメソッドを呼び出すと、エラーが「メイン」ストリームに続き、ストリームが停止します。

あなたができることは、エラーを別のアイテムに変換することです(ここで説明されているように: https://stackoverflow.com/a/28971140/47669 )が、メインストリームではありません(あなたが推測するように)既に試しました)が、mRestService.postLocations(locations)で。

この方法では、この呼び出しはエラーを発し、アイテム/別のオブザーバブルに変換されてから完了します。 (onErrorを呼び出さずに)。

コンシューマービューでは、mRestService.postLocations(locations)は1つのアイテムを発行し、すべてが成功した場合と同様に完了します。

mSubscription = reactiveLocationProvider.getUpdatedLocation(mLocationRequest)
        .buffer(50)
        .flatMap(locations -> mRestService.postLocations(locations).onErrorReturn((e) -> Collections.emptyList()) // can't throw exception
        .subscribeOn(Schedulers.newThread())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe();
15
dwursteisen

エラー処理演算子 のいずれかを使用できます。

  • onErrorResumeNext( ) — Observableにエラーが発生した場合にアイテムのシーケンスを発行するよう指示します
  • onErrorReturn( ) —エラーが発生したときに特定のアイテムを発行するようにObservableに指示します
  • onExceptionResumeNext( ) —例外に遭遇した後もアイテムを出力し続けるようにObservableに指示します(ただし、他の種類のスロー可能オブジェクトは例外ではありません)
  • retry( ) —ソースObservableがエラーを出力する場合、エラーなしで完了することを期待して再サブスクライブします
  • retryWhen( ) —ソースObservableがエラーを出力する場合、そのエラーを別のObservableに渡して、ソースを再サブスクライブするかどうかを決定します

特にretryonExceptionResumeNextはあなたの場合に有望に見えます。

51
tomrozb

flatMap内のエラーを単に無視したい場合要素を返さずにこれを行います:

flatMap(item -> 
    restService.getSomething(item).onErrorResumeNext(Observable.empty())
);
9

@MikeNの回答からリンク情報を貼り付けて、それが失われた場合に備えてください。

import rx.Observable.Operator;
import rx.functions.Action1;

public final class OperatorSuppressError<T> implements Operator<T, T> {
    final Action1<Throwable> onError;

    public OperatorSuppressError(Action1<Throwable> onError) {
        this.onError = onError;
    }

    @Override
    public Subscriber<? super T> call(final Subscriber<? super T> t1) {
        return new Subscriber<T>(t1) {

            @Override
            public void onNext(T t) {
                t1.onNext(t);
            }

            @Override
            public void onError(Throwable e) {
                onError.call(e);
            }

            @Override
            public void onCompleted() {
                t1.onCompleted();
            }

        };
    }
}

他のオペレーターはその前に熱心に退会する可能性があるため、観察可能なソースの近くで使用します。

Observerable.create(connectToUnboundedStream()).lift(new OperatorSuppressError(log()).doOnNext(someStuff()).subscribe();

ただし、これにより、ソースからのエラー配信が抑制されることに注意してください。チェーンに例外がスローされた後のonNextが存在する場合、それでもソースはサブスクライブ解除される可能性があります。

6
AllDayAmazing

Observable.defer呼び出しで残りのサービスを呼び出してみてください。この方法では、呼び出しごとに独自の 'onErrorResumeNext'を使用する機会が得られ、エラーによってメインストリームが完了しません。

reactiveLocationProvider.getUpdatedLocation(mLocationRequest)
  .buffer(50)
  .flatMap(locations ->
    Observable.defer(() -> mRestService.postLocations(locations))
      .onErrorResumeNext(<SOME_DEFAULT_TO_REACT_TO>)
  )
........

その解決策はもともとこのスレッドからのものです-> RxJava Observable and Subscriber for skip skip exception? ですが、あなたの場合でもうまくいくと思います。

1
meddle

この問題の解決策を追加します。

privider
    .compose(ignoreErrorsTransformer)
    .subscribe()

private final Observable.Transformer<ResultType, ResultType> ignoreErrorsTransformer =
        new Observable.Transformer<ResultType, ResultType>() {
            @Override
            public Observable<ResultType> call(Observable<ResultType> resultTypeObservable) {
                return resultTypeObservable
                        .materialize()
                        .filter(new Func1<Notification<ResultType>, Boolean>() {
                            @Override
                            public Boolean call(Notification<ResultType> resultTypeNotification) {
                                return !resultTypeNotification.isOnError();
                            }
                        })
                        .dematerialize();

            }
        };
1
HotIceCream

有限ストリームを完了するためのソリューション(@MikeN)のわずかな変更:

import rx.Observable.Operator;
import rx.functions.Action1;

public final class OperatorSuppressError<T> implements Operator<T, T> {
    final Action1<Throwable> onError;

    public OperatorSuppressError(Action1<Throwable> onError) {
        this.onError = onError;
    }

    @Override
    public Subscriber<? super T> call(final Subscriber<? super T> t1) {
        return new Subscriber<T>(t1) {

            @Override
            public void onNext(T t) {
                t1.onNext(t);
            }

            @Override
            public void onError(Throwable e) {
                onError.call(e);
                //this will allow finite streams to complete
                t1.onCompleted();
            }

            @Override
            public void onCompleted() {
                t1.onCompleted();
            }

        };
    }
}
0
portenez