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