web-dev-qa-db-fra.com

Meilleure pratique pour gérer onError et poursuivre le traitement

Je suis nouveau sur RxJava mais je l'intègre dans un projet sur lequel je travaille pour m'aider à l'apprendre. J'ai rencontré une question sur les meilleures pratiques.

J'ai une question sur la façon de gérer onError pour empêcher l'arrêt du traitement Observable.

Voici la configuration:

J'ai une liste d'ID utilisateur pour chacun, je voudrais faire 2 ou plusieurs requêtes réseau. Si l'une des demandes réseau échoue pour l'ID utilisateur, cet ID utilisateur ne sera pas mis à jour et pourra être ignoré. Cela ne doit pas empêcher le traitement des autres ID utilisateur. J'ai une solution, mais elle implique des abonnements imbriqués (voir le deuxième bloc de code). Un problème que je vois est que, si chaque appel échoue, il n'y a aucun moyen de court-circuiter et d'empêcher le reste de frapper une ressource réseau même après la détection d'un certain nombre de seuils ayant échoué.

Y a-t-il une meilleure manière de faire cela?

En code traditionnel:

List<String> results = new ArrayList<String>();
for (String userId : userIds) {
    try {
        String info = getInfo(userId);  // can throw an GetInfoException
        String otherInfo = getOtherInfo(userId);  // can throw an GetOtherInfoException
        results.add(info + ", " + otherInfo);
    } catch (GetInfoException e) {
        log.error(e);
    } catch (GetOtherInfoException e) {
        log.error(e);
    }
}

PROBLÈME:

Pseudocode:

userid -> network requests -> result 
1 -> a, b -> onNext(1[a ,b])
2 -> a, onError -> onError
3 -> a, b -> onNext(3[a, b])
4 -> a, b -> onNext(4[a, b])

Ce qui suit est un exemple de travail d'une liste d'ID utilisateur et pour chaque 2 demandes d'informations. Si vous l'exécutez, vous verrez qu'il échouera (voir ci-dessous le code source)

import rx.Observable;
import rx.Observable.OnSubscribeFunc;
import rx.Observer;
import rx.Subscription;
import rx.subscriptions.Subscriptions;
import rx.util.functions.Action0;
import rx.util.functions.Action1;
import rx.util.functions.Func1;

public class TestMergeDelayError {

    public static Observable<String> getUserIds() {
        return Observable.from(new String[]{"1", "2", "3", "4", "5", "6"});
    }

    public static Observable<String> getInfo(final String prefix, final String integer, final String errorNumber) {
        Observable<String> observable = Observable.create(new OnSubscribeFunc<String>() {

            public Subscription onSubscribe(Observer<? super String> t1) {
                if (integer.contains(errorNumber)) {
                    t1.onError(new Exception());
                } else {
                    t1.onNext(prefix + integer);
                    t1.onCompleted();
                }
                return Subscriptions.empty();
            }
        });
        return observable;
    }

    public static void main(String[] args) {

        Observable<String> userIdObservable = getUserIds();
        Observable<String> t = userIdObservable.flatMap(new Func1<String, Observable<String>>() {

            public Observable<String> call(final String t1) {
                Observable<String> info1 = getInfo("1::: ", t1, "2");
                Observable<String> info2 = getInfo("2::: ",t1, "3");
                return Observable.mergeDelayError(info1, info2);
            }
        });

        t.subscribe(new Action1<String>() {

            public void call(String t1) {
                System.out.println(t1);
            }
        }, new Action1<Throwable>() {

            public void call(Throwable t1) {
                t1.printStackTrace();
            }
        },
        new Action0(){

            public void call() {
                System.out.println("onComplete");
            }

        });
    }
}

Sortie:

1::: 1
2::: 1
2::: 2
Java.lang.Exception
        at TestMergeDelayError$1.onSubscribe(TestMergeDelayError.Java:32)
        at rx.Observable.subscribe(Observable.Java:241)
        at rx.operators.OperationMergeDelayError$MergeDelayErrorObservable$ParentObserver.onNext(OperationMergeDelayError.Java:266)
        at rx.operators.OperationMergeDelayError$MergeDelayErrorObservable$ParentObserver.onNext(OperationMergeDelayError.Java:210)
        at rx.operators.OperationMergeDelayError$2.onSubscribe(OperationMergeDelayError.Java:77)
        at rx.Observable.subscribe(Observable.Java:241)
        at rx.operators.OperationMergeDelayError$MergeDelayErrorObservable.onSubscribe(OperationMergeDelayError.Java:171)
        at rx.operators.OperationMergeDelayError$1.onSubscribe(OperationMergeDelayError.Java:64)
        at rx.Observable.subscribe(Observable.Java:241)
        at rx.operators.OperationMerge$MergeObservable$ParentObserver.onNext(OperationMerge.Java:164)
        at rx.operators.OperationMerge$MergeObservable$ParentObserver.onNext(OperationMerge.Java:116)
        at rx.operators.OperationMap$MapObservable$1.onNext(OperationMap.Java:105)
        at rx.operators.SafeObserver.onNext(SafeObserver.Java:102)
        at rx.operators.OperationToObservableIterable$ToObservableIterable.onSubscribe(OperationToObservableIterable.Java:94)
        at rx.Observable.subscribe(Observable.Java:241)
        at rx.operators.OperationMap$MapObservable.onSubscribe(OperationMap.Java:102)
        at rx.operators.OperationMap$2.onSubscribe(OperationMap.Java:76)
        at rx.Observable.subscribe(Observable.Java:241)
        at rx.operators.OperationMerge$MergeObservable.onSubscribe(OperationMerge.Java:106)
        at rx.operators.OperationMerge$1.onSubscribe(OperationMerge.Java:56)
        at rx.Observable.subscribe(Observable.Java:241)
        at rx.Observable.protectivelyWrapAndSubscribe(Observable.Java:320)
        at rx.Observable.subscribe(Observable.Java:483)

Solution d'abonnement imbriquée:

import rx.Observable;
import rx.Observable.OnSubscribeFunc;
import rx.Observer;
import rx.Subscription;
import rx.subscriptions.Subscriptions;
import rx.util.functions.Action0;
import rx.util.functions.Action1;
import rx.util.functions.Func1;

public class TestMergeDelayError {

    public static Observable<String> getUserIds() {
        return Observable.from(new String[]{"1", "2", "3", "4", "5", "6"});
    }

    public static Observable<String> getInfo(final String prefix, final String integer, final String errorNumber) {
        Observable<String> observable = Observable.create(new OnSubscribeFunc<String>() {

            public Subscription onSubscribe(Observer<? super String> t1) {
                if (integer.contains(errorNumber)) {
                    t1.onError(new Exception());
                } else {
                    t1.onNext(prefix + integer);
                    t1.onCompleted();
                }
                return Subscriptions.empty();
            }
        });
        return observable;
    }

    public static void main(String[] args) {

        Observable<String> userIdObservable = getUserIds();
        userIdObservable.subscribe(new Action1<String>() {

            public void call(String t1) {
                Observable<String> info1 = getInfo("1::: ", t1, "2");
                Observable<String> info2 = getInfo("2::: ", t1, "3");
                Observable.merge(info1, info2).subscribe(new Action1<String>() {

                    public void call(String t1) {
                        System.out.println(t1);
                    }
                }, new Action1<Throwable>() {

                    public void call(Throwable t1) {
                        t1.printStackTrace();
                    }
                },
                        new Action0() {

                            public void call() {
                                System.out.println("onComplete");
                            }

                        });
            }
        });
    }
}

Sortie:

1::: 1
2::: 1
onComplete
Java.lang.Exception
        at TestMergeDelayError$1.onSubscribe(TestMergeDelayError.Java:28)
        at rx.Observable.subscribe(Observable.Java:241)
        at rx.operators.OperationMerge$MergeObservable$ParentObserver.onNext(OperationMerge.Java:164)
        at rx.operators.OperationMerge$MergeObservable$ParentObserver.onNext(OperationMerge.Java:116)
        at rx.operators.OperationToObservableIterable$ToObservableIterable.onSubscribe(OperationToObservableIterable.Java:94)
        at rx.Observable.subscribe(Observable.Java:241)
        at rx.operators.OperationMerge$MergeObservable.onSubscribe(OperationMerge.Java:106)
        at rx.operators.OperationMerge$1.onSubscribe(OperationMerge.Java:56)
        at rx.Observable.subscribe(Observable.Java:241)
        at rx.Observable.protectivelyWrapAndSubscribe(Observable.Java:320)
        at rx.Observable.subscribe(Observable.Java:483)
        at TestMergeDelayError$2.call(TestMergeDelayError.Java:47)
        at TestMergeDelayError$2.call(TestMergeDelayError.Java:42)
        at rx.Observable$2.onNext(Observable.Java:381)
        at rx.operators.SafeObserver.onNext(SafeObserver.Java:102)
        at rx.operators.OperationToObservableIterable$ToObservableIterable.onSubscribe(OperationToObservableIterable.Java:94)
        at rx.Observable.subscribe(Observable.Java:241)
        at rx.Observable.protectivelyWrapAndSubscribe(Observable.Java:320)
        at rx.Observable.subscribe(Observable.Java:367)
        at TestMergeDelayError.main(TestMergeDelayError.Java:42)
1::: 3
Java.lang.Exception
        at TestMergeDelayError$1.onSubscribe(TestMergeDelayError.Java:28)
        at rx.Observable.subscribe(Observable.Java:241)
        at rx.operators.OperationMerge$MergeObservable$ParentObserver.onNext(OperationMerge.Java:164)
        at rx.operators.OperationMerge$MergeObservable$ParentObserver.onNext(OperationMerge.Java:116)
        at rx.operators.OperationToObservableIterable$ToObservableIterable.onSubscribe(OperationToObservableIterable.Java:94)
        at rx.Observable.subscribe(Observable.Java:241)
        at rx.operators.OperationMerge$MergeObservable.onSubscribe(OperationMerge.Java:106)
        at rx.operators.OperationMerge$1.onSubscribe(OperationMerge.Java:56)
        at rx.Observable.subscribe(Observable.Java:241)
        at rx.Observable.protectivelyWrapAndSubscribe(Observable.Java:320)
        at rx.Observable.subscribe(Observable.Java:483)
        at TestMergeDelayError$2.call(TestMergeDelayError.Java:47)
        at TestMergeDelayError$2.call(TestMergeDelayError.Java:42)
        at rx.Observable$2.onNext(Observable.Java:381)
        at rx.operators.SafeObserver.onNext(SafeObserver.Java:102)
        at rx.operators.OperationToObservableIterable$ToObservableIterable.onSubscribe(OperationToObservableIterable.Java:94)
        at rx.Observable.subscribe(Observable.Java:241)
        at rx.Observable.protectivelyWrapAndSubscribe(Observable.Java:320)
        at rx.Observable.subscribe(Observable.Java:367)
        at TestMergeDelayError.main(TestMergeDelayError.Java:42)
1::: 4
2::: 4
onComplete
1::: 5
2::: 5
onComplete
1::: 6
2::: 6
onComplete

Comme vous pouvez le voir, seuls les ID utilisateur individuels qui ont échoué ont interrompu leur traitement individuel, mais les autres ID utilisateur ont été traités.

À la recherche de conseils, voyez si cette solution est logique et si ce n'est pas la meilleure pratique.

Merci, Alex

26
Alex Beggs

La meilleure pratique consiste à utiliser mergeDelayError () qui combinent plusieurs observables en un seul, permettant aux observables sans erreur de continuer avant de propager les erreurs.

mergeDelayError se comporte comme merge. L'exception est lorsque l'un des observables fusionnés se termine avec une notification onError. Si cela se produit avec la fusion, l'Observable fusionné émet immédiatement une notification onError et se termine. mergeDelayError, d'un autre côté, suspendra le signalement de l'erreur jusqu'à ce qu'il ait donné à tout autre observable ne produisant pas d'erreur qu'il fusionne une chance de terminer l'émission de leurs éléments, et il émettra ceux-ci lui-même, et ne se terminera qu'avec une notification onError lorsque tous les autres observables fusionnés sont terminés.

9
Morteza Rastgoo

Puisque vous voulez ignorer l'erreur, vous pouvez essayer onErrorResumeNext(Observable.<String>empty());. Par exemple,

Observable<String> info1 = getInfo("1::: ", t1, "2").onErrorResumeNext(Observable.<String>empty());
Observable<String> info2 = getInfo("2::: ", t1, "3").onErrorResumeNext(Observable.<String>empty());
return Observable.merge(info1, info2);
18
zsxwing

En tant que débutant Rx, je cherchais également une réponse simple pour traiter les exceptions séparément et continuer à traiter l'événement suivant, mais je n'ai pas trouvé de réponses à ce que @Daniele Segato demandait. Voici une solution sans contrôle:

Les exemples ci-dessus supposent que vous avez le contrôle sur les observables, c'est-à-dire qu'une façon consiste à retarder les erreurs jusqu'à la fin en utilisant mergeDelayError OR renvoie un événement vide connu Observable pour chaque événement comme observable séparément en utilisant la fusion.

S'il s'agit d'une erreur d'événement source, vous pouvez utiliser lift pour créer un autre observable qui traite fondamentalement la valeur de l'Observable actuel avec élégance. La classe SimpleErrorEmitter simule un flux illimité qui peut parfois échouer.

Observable.create(new SimpleErrorEmitter())
        // transform errors to write to error stream
        .lift(new SuppressError<Integer>(System.err::println))
        .doOnNext(System.out::println)  // and everything else to console
        .subscribe();


class SimpleErrorEmitter implements OnSubscribe<Integer> {
@Override
public void call(Subscriber<? super Integer> subscriber) {
    subscriber.onNext(1);
    subscriber.onNext(2);

    subscriber.onError(new FooException());

    subscriber.onNext(3);
    subscriber.onNext(4);

    subscriber.onCompleted();
}

class SuppressError<T> implements Operator<T, T> {
final Action1<Throwable> onError;
public SuppressError(Action1<Throwable> onError) {
    this.onError = onError;
}
@Override
public Subscriber<? super T> call(Subscriber<? super T> t1) {
    return new Subscriber<T>(t1) {
        @Override
        public void onNext(T t) {
            t1.onNext(t);
        }
        @Override
        public void onError(Throwable e) { // handle errors using a separate function
            onError.call(e);
        }
        @Override
        public void onCompleted() {
            t1.onCompleted();
        }
    };
}

S'il s'agit d'une erreur de traitement d'abonné qui peut essayer/intercepter et continuer avec élégance

    Observable<Integer> justInts = justStrs.map((str) -> {
        try {
            return Integer.parseInt(str);
        } catch (NumberFormatException e) {
            return null;
        }
    });

J'essaie toujours de trouver un moyen simple de réessayer ou de retarder l'essai de l'échec et de continuer à partir du suivant.

    Observable<String> justStrs = Observable
            .just("1", "2", "three", "4", "5")  // or an unbounded stream
            // both these retrying from beginning 
            // when you delay or retry, if they are of known exception type
            .retryWhen(ex -> ex.flatMap(eachex -> {
                // for example, if it is a socket or timeout type of exception, try delaying it or retrying it
                if (eachex instanceof RuntimeException) {
                    return Observable.timer(1L, TimeUnit.MICROSECONDS, Schedulers.immediate());
                }
                return Observable.error(eachex);
            }))
            // or simply retry 2 times
            .retry(2) // if it is the source problem, attempt retry
            .doOnError((ex) -> System.err.println("On Error:" + ex));

Référence: https://groups.google.com/forum/#!topic/rxjava/trm2n6S4FSc

1
kisna

En regardant la source de Observable.flatMap:

return merge(map(func));

Si vous souhaitez que tous les ID utilisateur possibles soient traités, vous pouvez continuer avec une version modifiée de flatMap:

Observable.mergeDelayError(userIdObservable.map(userInfoFunc))

Plus loin, si vous dites:

Si l'une des demandes réseau échoue pour l'ID utilisateur, cet ID utilisateur ne sera pas mis à jour et peut être ignoré

Alors n'utilisez pas:

return Observable.mergeDelayError(info1, info2);

Parce que cela entraînera la demande de info1 et info2 même lorsque l'un d'eux échoue.

Allez plutôt avec:

return Observable.merge(info1, info2);

Lorsque info1 et info2 sont abonnés au même thread, elles s'exécuteront séquentiellement, donc si info1 échoue, info2 ne sera jamais demandé. Comme info1 et info2 sont limitées aux E/S, je suppose que vous voulez les exécuter en parallèle:

getInfo("1::: ", t1, "2").subscribeOn(Schedulers.io());
getInfo("2::: ",t1, "3").subscribeOn(Schedulers.io());

Cela devrait accélérer considérablement votre traitement

Le code entier:

public class TestMergeDelayError {

    public static Observable<String> getUserIds() {
        return Observable.from(new String[]{"1", "2", "3", "4", "5", "6"});
    }

    public static Observable<String> getInfo(final String prefix, final String integer, final String errorNumber) {
        return Observable.create(new OnSubscribeFunc<String>() {

            public Subscription onSubscribe(Observer<? super String> t1) {
                if (integer.contains(errorNumber)) {
                    t1.onError(new Exception());
                } else {
                    t1.onNext(prefix + integer);
                    t1.onCompleted();
                }
                return Subscriptions.empty();
            }
        })
        .subscribeOn(Schedulers.io());
    }

    public static void main(String[] args) {

        Observable<String> userIdObservable = getUserIds();
        Observable<String> t = Observable.mergeDelayError(userIdObservable.map(new Func1<String, Observable<String>>() {

            public Observable<String> call(final String t1) {
                Observable<String> info1 = getInfo("1::: ", t1, "2");
                Observable<String> info2 = getInfo("2::: ",t1, "3");
                return Observable.merge(info1, info2);
            }
        }));
        //rest is the same
    }
}
1
Tomas Bartalos