web-dev-qa-db-fra.com

RxJava: comment composer plusieurs observables avec des dépendances et collecter tous les résultats à la fin?

J'apprends RxJava et, comme ma première expérience, j'essaie de réécrire le code dans la première méthode run() dans ce code (cité sur le blog de Netflix comme problème RxJava peut aider à résoudre) pour améliorer son asynchronicité en utilisant RxJava, c'est-à-dire qu'il n'attend pas le résultat du premier Futur (f1.get()) avant de passer au reste du code.

f3 dépend de f1. Je vois comment gérer cela, flatMap semble faire l'affaire:

Observable<String> f3Observable = Observable.from(executor.submit(new CallToRemoteServiceA()))
    .flatMap(new Func1<String, Observable<String>>() {
        @Override
        public Observable<String> call(String s) {
            return Observable.from(executor.submit(new CallToRemoteServiceC(s)));
        }
    });

Suivant, f4 et f5 dépend de f2. J'ai ceci:

final Observable<Integer> f4And5Observable = Observable.from(executor.submit(new CallToRemoteServiceB()))
    .flatMap(new Func1<Integer, Observable<Integer>>() {
        @Override
        public Observable<Integer> call(Integer i) {
            Observable<Integer> f4Observable = Observable.from(executor.submit(new CallToRemoteServiceD(i)));
            Observable<Integer> f5Observable = Observable.from(executor.submit(new CallToRemoteServiceE(i)));
            return Observable.merge(f4Observable, f5Observable);
        }
    });

Ce qui commence à devenir bizarre (mergeen faire n'est probablement pas ce que je veux ...) mais me permet de le faire à la fin, pas tout à fait ce que je veux:

f3Observable.subscribe(new Action1<String>() {
    @Override
    public void call(String s) {
        System.out.println("Observed from f3: " + s);
        f4And5Observable.subscribe(new Action1<Integer>() {
            @Override
            public void call(Integer i) {
                System.out.println("Observed from f4 and f5: " + i);
            }
        });
    }
});

Cela me donne:

Observed from f3: responseB_responseA
Observed from f4 and f5: 140
Observed from f4 and f5: 5100

qui est tous les nombres, mais malheureusement j'obtiens les résultats dans des appels séparés, donc je ne peux pas tout à fait remplacer le println final dans le code original:

System.out.println(f3.get() + " => " + (f4.get() * f5.get()));

Je ne comprends pas comment accéder à ces deux valeurs de retour sur la même ligne. Je pense qu'il y a probablement une programmation fonctionnelle qui me manque ici. Comment puis-je faire ceci? Merci.

20
Steve Kehlet

Il semble que tout ce dont vous avez vraiment besoin est un peu plus d'encouragement et de perspective sur la façon dont RX est utilisé. Je vous suggère de lire plus dans la documentation ainsi que les diagrammes en marbre (je sais qu'ils ne sont pas toujours utiles). Je suggère également d'examiner la fonction lift() et les opérateurs.

  • L'intérêt d'un observable est de concaténer le flux de données et la manipulation de données en un seul objet
  • Le point d'appels à map, flatMap et filter est de manipuler les données dans votre flux de données
  • Le point de fusion est de combiner les flux de données
  • Le but des opérateurs est de vous permettre de perturber un flux constant d'observables et de définir vos propres opérations sur un flux de données. Par exemple, j'ai codé un opérateur à moyenne mobile. Cela résume ndoubles dans un Observable de doubles pour renvoyer un flux de moyennes mobiles. Le code ressemblait littéralement à ceci

    Observable movingAverage = Observable.from(mDoublesArray).lift(new MovingAverageOperator(frameSize))

Vous serez soulagé de constater que de nombreuses méthodes de filtrage que vous considérez comme acquises ont toutes lift() sous le capot.

Cela étant dit; tout ce qu'il faut pour fusionner plusieurs dépendances est:

  • changer toutes les données entrantes en un type de données standard à l'aide de map ou flatMap
  • fusion de types de données standard dans un flux
  • en utilisant des opérateurs personnalisés si un objet doit attendre un autre, ou si vous devez commander des données dans le flux. Attention: cette approche ralentira le flux
  • utiliser pour répertorier ou vous abonner pour collecter toutes ces données
19
user3407713

Modifier: quelqu'un a converti le texte suivant, que j'avais ajouté en tant que modification de la question, en une réponse, que j'apprécie et que je comprends peut-être bon SO chose à faire, cependant Je ne considère pas cela comme une réponse car ce n'est clairement pas la bonne façon de le faire. Je n'utiliserais jamais ce code et je ne conseillerais à personne de le copier. Autres/meilleures solutions et commentaires bienvenus!


J'ai pu résoudre ce problème avec les éléments suivants. Je ne savais pas que vous pouviez flatMap un observable plus d'une fois, j'ai supposé que les résultats ne pouvaient être consommés qu'une seule fois. J'ai donc juste flatMap f2Observable deux fois (désolé, j'ai renommé certaines choses dans le code depuis ma publication d'origine), puis Zip sur tous les observables, puis abonnez-vous. Ce Map dans le Zip pour agréger les valeurs n'est pas souhaitable en raison du type de jonglage. Autres/meilleures solutions et commentaires bienvenus! Le le code complet est visible dans un Gist . Je vous remercie.

Future<Integer> f2 = executor.submit(new CallToRemoteServiceB());
Observable<Integer> f2Observable = Observable.from(f2);
Observable<Integer> f4Observable = f2Observable
    .flatMap(new Func1<Integer, Observable<Integer>>() {
        @Override
        public Observable<Integer> call(Integer integer) {
            System.out.println("Observed from f2: " + integer);
            Future<Integer> f4 = executor.submit(new CallToRemoteServiceD(integer));
            return Observable.from(f4);
        }       
    });     

Observable<Integer> f5Observable = f2Observable
    .flatMap(new Func1<Integer, Observable<Integer>>() {
        @Override
        public Observable<Integer> call(Integer integer) {
            System.out.println("Observed from f2: " + integer);
            Future<Integer> f5 = executor.submit(new CallToRemoteServiceE(integer));
            return Observable.from(f5);
        }       
    });     

Observable.Zip(f3Observable, f4Observable, f5Observable, new Func3<String, Integer, Integer, Map<String, String>>() {
    @Override
    public Map<String, String> call(String s, Integer integer, Integer integer2) {
        Map<String, String> map = new HashMap<String, String>();
        map.put("f3", s);
        map.put("f4", String.valueOf(integer));
        map.put("f5", String.valueOf(integer2));
        return map;
    }       
}).subscribe(new Action1<Map<String, String>>() {
    @Override
    public void call(Map<String, String> map) {
        System.out.println(map.get("f3") + " => " + (Integer.valueOf(map.get("f4")) * Integer.valueOf(map.get("f5"))));
    }       
});     

Et cela me donne la sortie souhaitée:

responseB_responseA => 714000
7
Steve Kehlet

Je pense que ce que vous recherchez est switchmap. Nous avons rencontré un problème similaire où nous avons un service de session qui gère l'obtention d'une nouvelle session à partir d'une API, et nous avons besoin de cette session avant de pouvoir obtenir plus de données. Nous pouvons ajouter à la session observable qui renvoie le sessionToken pour une utilisation dans notre appel de données.

getSession renvoie un observable;

public getSession(): Observable<any>{
  if (this.sessionToken)
    return Observable.of(this.sessionToken);
  else if(this.sessionObservable)
    return this.sessionObservable;
  else {
    // simulate http call 
    this.sessionObservable = Observable.of(this.sessonTokenResponse)
    .map(res => {
      this.sessionObservable = null;
      return res.headers["X-Session-Token"];
    })
    .delay(500)
    .share();
    return this.sessionObservable;
  }
}

et getData prend cela observable et y ajoute.

public getData() {
  if (this.dataObservable)
    return this.dataObservable;
  else {
    this.dataObservable = this.sessionService.getSession()
      .switchMap((sessionToken:string, index:number) =>{
        //simulate data http call that needed sessionToken
          return Observable.of(this.dataResponse)
          .map(res => {
            this.dataObservable = null;
            return res.body;
          })
          .delay(1200)
        })
        .map ( data => {
          return data;
        })
        .catch(err => {
          console.log("err in data service", err);
         // return err;
        })
        .share();
    return this.dataObservable;
  }
}

Vous aurez toujours besoin d'un flatmap pour combiner les observables non dépendants.

Plunkr: http://plnkr.co/edit/hiA1jP?p=info

Où j'ai eu l'idée d'utiliser la carte de commutation: http://blog.oughttram.io/angular/2016/01/06/taking-advantage-of-observables-in-angular2.html

2
Stephen