Quando você usa map vs flatMap no RxJava?

180

Quando você usa mapvs flatMapno RxJava ?

Digamos, por exemplo, que queremos mapear arquivos que contêm JSON em Strings que contêm o JSON--

Usando map, temos que lidar com isso de Exceptionalguma forma. Mas como?:

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 :(
    }
});

Usando flatMap, é muito mais detalhado, mas podemos encaminhar o problema ao longo da cadeia Observablese lidar com o erro se escolhermos outro lugar e até tentar novamente:

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

Eu gosto da simplicidade do map, mas do tratamento de erros flatmap(não da verbosidade). Não vi nenhuma prática recomendada por aí e estou curioso para saber como isso está sendo usado na prática.

Christopher Perry
fonte

Respostas:

121

maptransformar um evento em outro. flatMaptransformar um evento em zero ou mais. (isso é retirado do IntroToRx )

Como você deseja transformar seu json em um objeto, usar map deve ser suficiente.

Lidar com o FileNotFoundException é outro problema (o uso do mapa ou do flatmap não resolveria esse problema).

Para resolver seu problema de exceção, basta lançá-lo com uma exceção não verificada: o RX chamará o manipulador onError para você.

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

exatamente a mesma versão do 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
        }
    }
});

Você também pode retornar, na versão flatMap, um novo Observable que é apenas um erro.

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));
        }
    }
});
dwursteisen
fonte
2
Isso não chama subscriber.onError()etc. Todos os exemplos que eu vi rotearam erros dessa maneira. Isso não importa?
Christopher Perry
7
Observe que os construtores de OnErrorThrowablesão privatee você precisa usar OnErrorThrowable.from(e).
David.mihola
Acabei de atualizar. OnErrorThrowable.from (e) não mantém o valor, então eu uso OnErrorThrowable.addValueAsLastCause (e, file), que deve manter o valor.
Dwursteisen 27/05
1
Gosto dos exemplos de código, mas ajudaria se você atualizasse a assinatura das chamadas flatMap para retornar Observable <String> em vez de apenas String ... porque essa não é tecnicamente a diferença entre os dois?
Rich Ehmer
78

O FlatMap se comporta de maneira muito semelhante ao mapa, a diferença é que a função que ele aplica retorna um observável, portanto é perfeitamente adequado para mapear operações assíncronas.

No sentido prático, a função Map aplica apenas faz uma transformação sobre a resposta encadeada (não retornando um Observável); enquanto a função que o FlatMap aplica retorna Observable<T>, é por isso que o FlatMap é recomendado se você planeja fazer uma chamada assíncrona dentro do método.

Resumo:

  • Mapa retorna um objeto do tipo T
  • O FlatMap retorna um Observable.

Um exemplo claro pode ser visto aqui: http://blog.couchbase.com/why-couchbase-chose-rxjava-new-java-sdk .

O Couchbase Java 2.X Client usa o Rx para fornecer chamadas assíncronas de maneira conveniente. Como ele usa o Rx, ele possui o mapa de métodos e o FlatMap, a explicação na documentação pode ser útil para entender o conceito geral.

Para lidar com erros, substitua onError no seu assinante.

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

Pode ajudar a olhar para este documento: http://blog.danlew.net/2014/09/15/grokking-rxjava-part-1/

Uma boa fonte sobre como gerenciar erros com o RX pode ser encontrada em: https://gist.github.com/daschl/db9fcc9d2b932115b679

1vand1ng0
fonte
O resumo está errado. Map e FlatMap retornam o mesmo tipo, mas a função que eles aplicam retornam tipos diferentes.
CoXier 9/0318
61

No seu caso, você precisa de um mapa, pois há apenas 1 entrada e 1 saída.

A função fornecida pelo mapa simplesmente aceita um item e retorna um item que será emitido mais (apenas uma vez) para baixo.

A função flatMap - fornecida aceita um item e retorna um "Observável", o que significa que cada item do novo "Observável" será emitido separadamente mais adiante.

Pode ser que o código esclareça as coisas para você:

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

Resultado:

inside the map item1
item1
inside the flatMap item2
item2+
item2++
item2+++
mt.uulu
fonte
Não tenho certeza se o uso de um mapa é a melhor ideia, embora funcione. Suponha que o FileReader se torne uma chamada assíncrona. Então você precisa alterar o mapa para um flatMap. Deixá-lo como um mapa significaria que você não seria acionado conforme o esperado e causará confusão. Fui mordido por isso algumas vezes, pois ainda estou aprendendo RX Java. Acho que o flatMap é uma maneira infalível de garantir que as coisas sejam processadas conforme o esperado.
user924272
24

A maneira como penso sobre isso é que você usa flatMapquando a função que você deseja colocar dentro de map()retorna um Observable. Nesse caso, você ainda pode tentar usar, map()mas seria impraticável. Deixe-me tentar explicar o porquê.

Se, nesse caso, você decidisse mapcontinuar, obteria um Observable<Observable<Something>>. Por exemplo, no seu caso, se usássemos uma biblioteca RxGson imaginária, que retornasse um método Observable<String>from toJson()(do que simplesmente retorne a String), ficaria assim:

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

Neste ponto, seria bastante complicado para subscribe()um observável. Dentro dele, você obteria um valor Observable<String>ao qual seria necessário subscribe()obter novamente o valor. O que não é prático ou agradável de se olhar.

Portanto, para tornar útil uma idéia, é "achatar" esse observável de observáveis ​​(você pode começar a ver de onde vem o nome _flat_Map). O RxJava fornece algumas maneiras de nivelar os observáveis ​​e, por uma questão de simplicidade, vamos assumir que a mesclagem é o que queremos. A mesclagem basicamente pega um monte de observáveis ​​e emite sempre que algum deles é emitido. (Muitas pessoas argumentam que a troca seria um padrão melhor. Mas se você estiver emitindo apenas um valor, isso não importa).

Assim, alterando nosso snippet anterior, obteríamos:

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

Isso é muito mais útil, porque, ao se inscrever nisso (ou mapear, filtrar ou ...), você apenas obtém o Stringvalor. (Além disso, lembre-se, essa variante de merge()não existe no RxJava, mas se você entender a idéia de mesclagem, espero que você também entenda como isso funcionaria.)

Então, basicamente porque isso merge()provavelmente só deve ser útil quando for possível map()retornar um observável e, assim, você não precisará digitar isso repetidamente, flatMap()foi criado como uma abreviação. Ele aplica a função de mapeamento da mesma maneira que map()faria normalmente , mas mais tarde, em vez de emitir os valores retornados, também os "achata" (ou mescla).

Esse é o caso de uso geral. É mais útil em uma base de código que usa Rx em todo o lugar e você tem muitos métodos retornando observáveis, que você deseja encadear com outros métodos retornando observáveis.

Em seu caso de uso ele passa a ser útil também, porque map()só pode transformar um valor emitido em onNext()em outro valor emitido em onNext(). Mas não pode transformá-lo em vários valores, nenhum valor ou um erro. E como o akarnokd escreveu em sua resposta (e lembre-se de que ele é muito mais esperto que eu, provavelmente em geral, mas pelo menos no que diz respeito ao RxJava), você não deve lançar exceções no seu map(). Então, em vez disso, você pode usar flatMap()e

return Observable.just(value);

quando tudo vai bem, mas

return Observable.error(exception);

quando algo falha.
Veja sua resposta para obter um trecho completo: https://stackoverflow.com/a/30330772/1402641

Marcin Koziński
fonte
1
esta é a minha resposta preferida. você basicamente acaba aninhando um IN observável em um IF observável, que é o que seu método retorna.
precisa saber é o seguinte
21

A questão é: quando você usa map vs flatMap no RxJava? . E acho que uma demonstração simples é mais específica.

Quando você deseja converter o item emitido para outro tipo, no seu caso, converter o arquivo em String, map e flatMap podem funcionar. Mas eu prefiro o operador de mapa porque é mais claro.

No entanto, em algum lugar, flatMappode fazer um trabalho mágico, mas mapnão pode. Por exemplo, quero obter as informações de um usuário, mas primeiro preciso obter o ID dele quando o usuário efetuar login. Obviamente, preciso de duas solicitações e elas estão em ordem.

Vamos começar.

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

Observable<UserInfo> fetchUserInfo(String userId);

Aqui estão dois métodos, um para login retornado Responsee outro para buscar informações do usuário.

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

Como você vê, na função flatMap se aplica, primeiro eu obtenho o ID do usuário e Responsedepois busco informações do usuário. Quando duas solicitações são concluídas, podemos executar nosso trabalho, como atualizar a interface do usuário ou salvar dados no banco de dados.

No entanto, se você usar, mapnão poderá escrever um código tão bom. Em uma palavra, flatMappode nos ajudar a serializar solicitações.

CoXier
fonte
18

Aqui é um simples polegar regra que eu uso me ajudar a decidir como quando usar flatMap()mais map()no Rx de Observable.

Quando você decide que vai empregar uma maptransformação, escreveria seu código de transformação para retornar algum objeto, certo?

Se o que você está retornando como resultado final de sua transformação é:

  • um objeto não observável, você usaria apenasmap() . E map()envolve esse objeto em um Observável e o emite.

  • um Observableobjeto, então você usariaflatMap() . E flatMap()desembrulha o Observable, pega o objeto retornado, envolve-o com seu próprio Observable e emite-o.

Digamos, por exemplo, que temos um método titleCase (String inputParam) que retorna o objeto Titled Cased String do parâmetro de entrada. O tipo de retorno desse método pode ser Stringou Observable<String>.

  • Se o tipo de retorno titleCase(..)fosse mero String, você usariamap(s -> titleCase(s))

  • Se o tipo de retorno titleCase(..)fosse Observable<String>, você usariaflatMap(s -> titleCase(s))

Espero que isso esclareça.

karthiks
fonte
11

Eu só queria acrescentar que flatMap, você realmente não precisa usar seu próprio Observable personalizado dentro da função e pode confiar em métodos / operadores de fábrica padrão:

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

Geralmente, você deve evitar lançar exceções (tempo de execução) dos métodos onXXX e retornos de chamada, se possível, mesmo que tenhamos colocado tantas salvaguardas quanto pudemos no RxJava.

akarnokd
fonte
Mas acho que o mapa é suficiente. Então flatMap e mapa é um hábito, certo?
CoXier
6

Nesse cenário, use map, você não precisa de um novo Observable para ele.

você deve usar Exceptions.propagate, que é um invólucro para poder enviar essas exceções verificadas ao mecanismo 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
        } 
    } 
});

Você deve então lidar com esse erro no assinante

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

Existe uma excelente publicação para isso: http://blog.danlew.net/2015/12/08/error-handling-in-rxjava/

ndori
fonte
0

Em alguns casos, você pode acabar tendo uma cadeia de observáveis, em que seu observável retornaria outro observável. 'flatmap' meio que desembrulha o segundo observável que está oculto no primeiro e permite acessar diretamente os dados que o segundo observável está cuspindo durante a assinatura.

Anoop Isaac
fonte
0

O Flatmap mapeia observáveis ​​para observáveis. Mapeie itens para itens.

O Flatmap é mais flexível, mas o Mapa é mais leve e direto, portanto depende do seu caso de uso.

Se você estiver fazendo QUALQUER COISA assíncrona (incluindo a troca de threads), deverá usar o Flatmap, pois o Map não verificará se o consumidor está descartado (parte do peso leve)

skr1p7k1dd
fonte