web-dev-qa-db-fra.com

Quand utilisez-vous map vs flatMap dans RxJava?

Quand utilisez-vous map vs flatMap dans RxJava?

Disons, par exemple, nous voulons mapper des fichiers contenant du JSON en chaînes contenant le JSON--

En utilisant map, nous devons gérer l’exception d’une manière ou d’une autre. Mais comment?:

Observable.from(jsonFile).map(new Func1<File, String>() {
    @Override public String call(File file) {
        try {
            return new Gson().toJson(new FileReader(file), Object.class);
        } catch (FileNotFoundException e) {
            // So Exception. What to do ?
        }
        return null; // Not good :(
    }
});

En utilisant flatMap, c'est beaucoup plus détaillé, mais nous pouvons transférer le problème dans la chaîne des observables et gérer l'erreur si nous choisissons ailleurs et que nous réessayons même:

Observable.from(jsonFile).flatMap(new Func1<File, Observable<String>>() {
    @Override public Observable<String> call(final File file) {
        return Observable.create(new Observable.OnSubscribe<String>() {
            @Override public void call(Subscriber<? super String> subscriber) {
                try {
                    String json = new Gson().toJson(new FileReader(file), Object.class);

                    subscriber.onNext(json);
                    subscriber.onCompleted();
                } catch (FileNotFoundException e) {
                    subscriber.onError(e);
                }
            }
        });
    }
});

J'aime la simplicité de la carte, mais la gestion des erreurs de flatmap (pas la verbosité). Je n'ai pas vu de meilleures pratiques sur ce sujet et je suis curieux de savoir comment cela est utilisé dans la pratique.

170

map transformer un événement en un autre. flatMap transformer un événement en zéro événement ou plus. (ceci provient de IntroToRx )

Si vous voulez transformer votre JSON en objet, utiliser map devrait suffire.

La gestion de l'exception FileNotFoundException est un autre problème (utiliser map ou flatmap ne résoudrait pas ce problème).

Pour résoudre votre problème d'exception, lancez-le avec une exception non cochée: RX appellera le gestionnaire onError pour vous.

Observable.from(jsonFile).map(new Func1<File, String>() {
    @Override public String call(File file) {
        try {
            return new Gson().toJson(new FileReader(file), Object.class);
        } catch (FileNotFoundException e) {
            // this exception is a part of rx-Java
            throw OnErrorThrowable.addValueAsLastCause(e, file);
        }
    }
});

la même version avec flatmap:

Observable.from(jsonFile).flatMap(new Func1<File, Observable<String>>() {
    @Override public Observable<String> call(File file) {
        try {
            return Observable.just(new Gson().toJson(new FileReader(file), Object.class));
        } catch (FileNotFoundException e) {
            // this static method is a part of rx-Java. It will return an exception which is associated to the value.
            throw OnErrorThrowable.addValueAsLastCause(e, file);
            // alternatively, you can return Obersable.empty(); instead of throwing exception
        }
    }
});

Vous pouvez également renvoyer, dans la version flatMap, un nouvel observable qui est simplement une erreur.

Observable.from(jsonFile).flatMap(new Func1<File, Observable<String>>() {
    @Override public Observable<String> call(File file) {
        try {
            return Observable.just(new Gson().toJson(new FileReader(file), Object.class));
        } catch (FileNotFoundException e) {
            return Observable.error(OnErrorThrowable.addValueAsLastCause(e, file));
        }
    }
});
114
dwursteisen

FlatMap se comporte beaucoup comme map, la différence est que la fonction qu’elle applique renvoie un observable lui-même, il est donc parfaitement adapté à la cartographie des opérations asynchrones.

Dans la pratique, la fonction Map s’applique simplement pour transformer la réponse enchaînée (ne pas renvoyer d’observable); alors que la fonction FlatMap s'applique renvoie un Observable<T>, c'est pourquoi FlatMap est recommandé si vous prévoyez de passer un appel asynchrone dans la méthode.

Sommaire:

  • La carte retourne un objet de type T
  • FlatMap renvoie un observable.

Un exemple clair peut être vu ici: http://blog.couchbase.com/why-couchbase-chose-rxjava-new-Java-sdk .

Couchbase Java 2.X Le client utilise Rx pour fournir des appels asynchrones de manière pratique. Puisqu'il utilise Rx, il dispose des méthodes map et FlatMap, l'explication fournie dans leur documentation peut être utile pour comprendre le concept général.

Pour gérer les erreurs, remplacez onError sur votre abonné.

Subscriber<String> mySubscriber = new Subscriber<String>() {
    @Override
    public void onNext(String s) { System.out.println(s); }

    @Override
    public void onCompleted() { }

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

Il pourrait être utile de consulter ce document: http://blog.danlew.net/2014/09/15/grokking-rxjava-part-1/

Vous trouverez une bonne source d'informations sur la gestion des erreurs avec RX à l'adresse suivante: https://Gist.github.com/daschl/db9fcc9d2b932115b679

75
1vand1ng0

Dans votre cas, vous avez besoin d'une carte, car il n'y a qu'une entrée et une sortie.

la fonction fournie par map accepte simplement un élément et renvoie un élément qui sera émis (une seule fois) par la suite.

flatMap - la fonction fournie accepte un élément, puis renvoie un "Observable", ce qui signifie que chaque élément du nouveau "Observable" sera émis séparément plus bas.

Peut-être que le code clarifiera les choses pour vous:

Observable.just("item1").map( str -> {
    System.out.println("inside the map " + str);
    return str;
}).subscribe(System.out::println);

Observable.just("item2").flatMap( str -> {
    System.out.println("inside the flatMap " + str);
    return Observable.just(str + "+", str + "++" , str + "+++");
}).subscribe(System.out::println);

Sortie:

inside the map item1
item1
inside the flatMap item2
item2+
item2++
item2+++
53
mt.uulu

Selon moi, vous utilisez flatMap lorsque la fonction que vous souhaitez insérer dans map() renvoie un Observable. Dans ce cas, vous pouvez toujours essayer d'utiliser map(), mais ce ne serait pas pratique. Laissez-moi essayer d'expliquer pourquoi.

Si, dans un tel cas, vous décidiez de vous en tenir à map, vous obtiendrez un Observable<Observable<Something>>. Par exemple, dans votre cas, si nous utilisions une bibliothèque imaginaire RxGson, celle-ci renvoyait une méthode Observable<String> à partir de sa méthode toJson() (au lieu de simplement renvoyer une String):

Observable.from(jsonFile).map(new Func1<File, Observable<String>>() {
    @Override public Observable<String>> call(File file) {
        return new RxGson().toJson(new FileReader(file), Object.class);
    }
}); // you get Observable<Observable<String>> here

À ce stade, il serait assez difficile de subscribe() d’être aussi observable. À l'intérieur, vous obtiendrez un Observable<String> auquel vous auriez à nouveau besoin de subscribe() pour obtenir la valeur. Ce qui n’est ni pratique ni agréable à regarder.

Donc, pour le rendre utile, une idée consiste à "aplatir" cet observable d'observables (vous pourriez commencer à voir d'où vient le nom _flat_Map). RxJava fournit quelques moyens pour aplatir les éléments observables et, dans un souci de simplicité, supposons que fusion est ce que nous voulons. La fusion prend essentiellement un tas d'observables et émet chaque fois que l'un d'eux émet. (Beaucoup de gens diraient que switch serait un meilleur choix. Mais si vous n'émettez qu'une seule valeur, cela n'a pas d'importance quand même.)

Donc, en modifiant notre extrait précédent, nous aurions:

Observable.from(jsonFile).map(new Func1<File, Observable<String>>() {
    @Override public Observable<String>> call(File file) {
        return new RxGson().toJson(new FileReader(file), Object.class);
    }
}).merge(); // you get Observable<String> here

C'est beaucoup plus utile, car en vous abonnant à cela (ou à un mappage, ou à un filtrage, ou ...), vous obtenez simplement la valeur String. (En outre, remarquez, une telle variante de merge() n'existe pas dans RxJava, mais si vous comprenez l'idée de fusion, j'espère que vous comprenez également comment cela fonctionnerait.)

Donc, fondamentalement, parce qu'une telle merge() ne devrait probablement être utile que lorsqu'elle réussit une map() renvoyant un observable et que vous n'avez donc pas besoin de taper ceci encore et encore, flatMap() a été créé en tant que un raccourci. Il applique la fonction de mappage exactement comme le ferait une map() normale, mais plus tard, au lieu d’émettre les valeurs renvoyées, il les "aplatit" (ou les fusionne).

C'est le cas d'utilisation général. Cela est très utile dans une base de code qui utilise Rx partout et vous avez de nombreuses méthodes renvoyant des observables, que vous souhaitez chaîner avec d'autres méthodes renvoyant des observables.

Dans votre cas d'utilisation, cela s'avère également utile, car map() ne peut transformer qu'une seule valeur émise dans onNext() en une autre valeur émise dans onNext(). Mais il ne peut pas le transformer en plusieurs valeurs, aucune valeur ou une erreur. Et comme akarnokd a écrit dans sa réponse (et remarquez qu'il est beaucoup plus intelligent que moi, probablement en général, mais au moins en ce qui concerne RxJava), vous ne devriez pas lancer d'exceptions de votre map() . Vous pouvez donc utiliser flatMap() et

return Observable.just(value);

quand tout va bien, mais

return Observable.error(exception);

quand quelque chose échoue.
Voir sa réponse pour un extrait complet: https://stackoverflow.com/a/30330772/1402641

23
Marcin Koziński

Voici un simple règle-du-pouce que j'utilise aide-moi à décider quand utiliser flatMap() sur map() dans Rx's Observable.

Une fois que vous avez décidé d'utiliser une transformation map, vous écrivez votre code de transformation pour renvoyer un objet Object, n'est-ce pas?

Si ce que vous retournez comme résultat final de votre transformation est:

  • n objet non observable, vous utiliseriez alors simplement map(). Et map() encapsule cet objet dans un observable et l’émet.

  • n objet Observable, vous utiliseriez alors flatMap(). Et flatMap() déballe l'Observable, sélectionne l'objet renvoyé, l'enveloppe de son propre Observable et l'émet.

Imaginons par exemple une méthode titleCase (String inputParam) qui renvoie l'objet Titled Cased String du paramètre d'entrée. Le type de retour de cette méthode peut être String ou Observable<String>.

  • Si le type de retour de titleCase(..) devait être simple String, vous utiliseriez alors map(s -> titleCase(s))

  • Si le type de retour de titleCase(..) devait être Observable<String>, vous utiliseriez alors flatMap(s -> titleCase(s)).

J'espère que ça clarifie.

16
karthiks

La question est Quand utilisez-vous map vs flatMap dans RxJava?. Et je pense qu'une simple démo est plus spécifique.

Lorsque vous souhaitez convertir un élément émis en un autre type, dans votre cas, la conversion de fichier en chaîne, map et flatMap peut fonctionner. Mais je préfère l'opérateur de carte parce que c'est plus clair.

Cependant, dans certains endroits, flatMap peut faire un travail magique mais map ne le peut pas. Par exemple, je veux obtenir les informations d'un utilisateur, mais je dois d'abord obtenir son identifiant lorsque l'utilisateur se connecte. De toute évidence, j'ai besoin de deux demandes et elles sont en ordre.

Commençons.

Observable<LoginResponse> login(String email, String password);

Observable<UserInfo> fetchUserInfo(String userId);

Voici deux méthodes, une pour la connexion retournée Response, et une autre pour récupérer les informations de l'utilisateur.

login(email, password)
        .flatMap(response ->
                fetchUserInfo(response.id))
        .subscribe(userInfo -> {
            // get user info and you update ui now
        });

Comme vous le voyez, dans la fonction flatMap, d'abord, j'obtiens l'ID utilisateur de Response, puis je récupère les informations sur l'utilisateur. Lorsque deux demandes sont terminées, nous pouvons effectuer notre travail, par exemple mettre à jour l'interface utilisateur ou enregistrer des données dans une base de données.

Cependant, si vous utilisez map, vous ne pouvez pas écrire un tel code de Nice. Dans un Word, flatMap peut nous aider à sérialiser des demandes.

16
CoXier

Je voulais juste ajouter qu'avec flatMap, vous n'avez pas vraiment besoin d'utiliser votre propre observable personnalisé dans la fonction et vous pouvez vous fier aux méthodes/opérateurs d'usine standard:

Observable.from(jsonFile).flatMap(new Func1<File, Observable<String>>() {
    @Override public Observable<String> call(final File file) {
        try {
            String json = new Gson().toJson(new FileReader(file), Object.class);
            return Observable.just(json);
        } catch (FileNotFoundException ex) {
            return Observable.<String>error(ex);
        }
    }
});

En règle générale, évitez autant que possible de lancer des exceptions (Runtime) à partir de méthodes onXXX, même si nous avons placé autant de sauvegardes que possible dans RxJava.

11
akarnokd

Dans ce scénario, utilisez la carte, vous n'avez pas besoin d'un nouvel observable.

vous devriez utiliser Exceptions.propagate, qui est un wrapper pour pouvoir envoyer les exceptions vérifiées au mécanisme rx

Observable<String> obs = Observable.from(jsonFile).map(new Func1<File, String>() { 
    @Override public String call(File file) {
        try { 
            return new Gson().toJson(new FileReader(file), Object.class);
        } catch (FileNotFoundException e) {
            throw Exceptions.propagate(t); /will propagate it as error
        } 
    } 
});

Vous devez alors gérer cette erreur dans l'abonné

obs.subscribe(new Subscriber<String>() {
    @Override 
    public void onNext(String s) { //valid result }

    @Override 
    public void onCompleted() { } 

    @Override 
    public void onError(Throwable e) { //e might be the FileNotFoundException you got }
};); 

Il y a un excellent post pour cela: http://blog.danlew.net/2015/12/08/error-handling-in-rxjava/

6
ndori

Dans certains cas, vous pourriez vous retrouver avec une chaîne d'observables, votre observable retournant un autre observable. Le type "flatmap" ouvre le deuxième observable qui est enterré dans le premier et vous permet d'accéder directement aux données que le deuxième observable crache tout en vous abonnant.

0
Anoop Isaac