Kapan Anda menggunakan peta vs flatMap di RxJava?

180

Kapan Anda menggunakan mapvs flatMapdi RxJava ?

Katakanlah, misalnya, kami ingin memetakan File yang berisi JSON ke dalam Strings yang berisi JSON--

Menggunakan map, kita harus berurusan dengan Exceptionentah bagaimana. Tapi bagaimana caranya?:

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

Menggunakan flatMap, ini jauh lebih bertele-tele, tetapi kita bisa meneruskan masalah ke bawah rantai Observablesdan menangani kesalahan jika kita memilih tempat lain dan bahkan coba lagi:

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

Saya suka kesederhanaan map, tetapi penanganan kesalahan flatmap(bukan verbositas). Saya belum melihat praktik terbaik tentang hal ini dan saya ingin tahu bagaimana ini digunakan dalam praktik.

Christopher Perry
sumber

Jawaban:

121

mapmengubah satu peristiwa ke peristiwa lainnya. flatMapmengubah satu peristiwa menjadi nol atau lebih peristiwa. (ini diambil dari IntroToRx )

Saat Anda ingin mengubah json Anda menjadi objek, menggunakan peta sudah cukup.

Berurusan dengan FileNotFoundException adalah masalah lain (menggunakan peta atau flatmap tidak akan menyelesaikan masalah ini).

Untuk mengatasi masalah Pengecualian Anda, buang saja dengan pengecualian Tidak dicentang: RX akan memanggil penangan onError untuk Anda.

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

versi yang sama persis dengan 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
        }
    }
});

Anda juga dapat kembali, dalam versi flatMap, sebuah Observable baru yang hanya merupakan kesalahan.

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
sumber
2
Ini tidak memanggil subscriber.onError()dll. Semua contoh yang saya lihat memiliki kesalahan yang dialihkan seperti itu. Apakah itu tidak masalah?
Christopher Perry
7
Perhatikan bahwa konstruktor dari OnErrorThrowableare privatedan Anda perlu menggunakan OnErrorThrowable.from(e)sebagai gantinya.
david.mihola
Saya baru saja memperbarui. OnErrorThrowable.from (e) tidak menyimpan nilainya, jadi saya menggunakan OnErrorThrowable.addValueAsLastCause (e, file) sebagai gantinya, yang seharusnya menjaga nilainya.
dwursteisen
1
Saya suka contoh kode, tetapi akan membantu jika Anda memperbarui tanda tangan panggilan flatMap untuk mengembalikan <String> yang Dapat Diamati daripada hanya String ... karena bukankah secara teknis perbedaan antara keduanya?
Rich Ehmer
78

FlatMap berperilaku sangat mirip dengan peta, perbedaannya adalah bahwa fungsi yang diterapkannya mengembalikan suatu yang dapat diamati sendiri, sehingga sangat cocok untuk memetakan pada operasi yang tidak sinkron.

Dalam arti praktis, fungsi Peta berlaku hanya membuat transformasi atas respon dirantai (tidak mengembalikan yang Teramati); sementara fungsi FlatMap berlaku mengembalikan sebuahObservable<T> , itu sebabnya FlatMap direkomendasikan jika Anda berencana untuk membuat panggilan tidak sinkron di dalam metode.

Ringkasan:

  • Peta mengembalikan objek bertipe T
  • FlatMap mengembalikan Observable.

Contoh yang jelas dapat dilihat di sini: http://blog.couchbase.com/why-couchbase-chose-rxjava-new-java-sdk .

Couchbase Java 2.X Client menggunakan Rx untuk menyediakan panggilan asinkron dengan cara yang nyaman. Karena menggunakan Rx, ia memiliki metode peta dan FlatMap, penjelasan dalam dokumentasi mereka mungkin membantu untuk memahami konsep umum.

Untuk menangani kesalahan, ganti onError pada susbcriber Anda.

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

Mungkin membantu untuk melihat dokumen ini: http://blog.danlew.net/2014/09/15/grokking-rxjava-part-1/

Sumber yang baik tentang cara mengelola kesalahan dengan RX dapat ditemukan di: https://gist.github.com/daschl/db9fcc9d2b932115b679

1vand1ng0
sumber
Ringkasan itu salah. Peta dan FlatMap mengembalikan tipe yang sama tetapi fungsi yang mereka terapkan menghasilkan tipe yang berbeda.
CoXier
61

Dalam kasus Anda, Anda perlu peta, karena hanya ada 1 input dan 1 output.

Fungsi peta - yang disediakan hanya menerima item dan mengembalikan item yang akan dipancarkan lebih lanjut (hanya sekali) ke bawah.

flatMap - fungsi yang disediakan menerima item kemudian mengembalikan "Dapat Diobservasi", yang berarti setiap item dari "Dapat Diamati" baru akan dipancarkan secara terpisah lebih jauh ke bawah.

Mungkin kode akan menjelaskan semuanya untuk Anda:

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

Keluaran:

inside the map item1
item1
inside the flatMap item2
item2+
item2++
item2+++
mt.uulu
sumber
Tidak yakin apakah menggunakan peta adalah ide terbaik, meskipun itu akan berhasil. Misalkan FileReader menjadi panggilan asinkron. Maka Anda perlu mengubah peta menjadi flatMap. Membiarkannya sebagai peta akan berarti Anda tidak akan memecat acara seperti yang diharapkan, dan akan menyebabkan kebingungan. Saya telah digigit oleh ini beberapa kali karena saya masih belajar RX Java. Saya menemukan flatMap adalah cara ampuh untuk memastikan hal-hal diproses seperti yang Anda harapkan.
user924272
24

Cara saya berpikir tentang hal itu adalah bahwa Anda menggunakan flatMapketika fungsi yang Anda ingin masukkan map()kembali Observable. Dalam hal ini Anda mungkin masih mencoba menggunakan map()tetapi itu tidak praktis. Biarkan saya mencoba menjelaskan mengapa.

Jika dalam kasus seperti itu Anda memutuskan untuk tetap dengan map, Anda akan mendapatkan Observable<Observable<Something>>. Misalnya dalam kasus Anda, jika kami menggunakan pustaka RxGson imajiner, yang mengembalikan metode Observable<String>dari itu toJson()(bukan hanya mengembalikan a String) itu akan terlihat seperti ini:

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

Pada titik ini akan sangat sulit untuk subscribe()diamati. Di dalamnya Anda akan mendapatkan Observable<String>yang Anda butuhkan lagi subscribe()untuk mendapatkan nilai. Yang tidak praktis atau enak dilihat.

Jadi untuk membuatnya berguna satu ide adalah untuk "meratakan" ini dapat diamati dari diamati (Anda mungkin mulai melihat dari mana nama _flat_Map berasal). RxJava menyediakan beberapa cara untuk meratakan yang dapat diamati dan demi kesederhanaan mari kita asumsikan penggabungan adalah apa yang kita inginkan. Penggabungan pada dasarnya mengambil banyak yang bisa diobservasi dan memancarkan setiap kali salah satu dari mereka memancarkan. (Banyak orang akan berpendapat bahwa beralih akan menjadi default yang lebih baik. Tetapi jika Anda hanya memancarkan satu nilai, itu tidak masalah.)

Jadi ubah cuplikan kami sebelumnya yang akan kami dapatkan:

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

Ini jauh lebih bermanfaat, karena dengan berlangganan (atau memetakan, atau memfilter, atau ...) Anda baru saja mendapatkan Stringnilainya. (Juga, ingatlah Anda, varian semacam merge()itu tidak ada di RxJava, tetapi jika Anda memahami gagasan penggabungan maka saya harap Anda juga memahami cara kerjanya.)

Jadi pada dasarnya karena itu merge()mungkin hanya akan berguna ketika berhasil map()mengembalikan yang dapat diamati dan jadi Anda tidak perlu mengetik ini berulang-ulang, flatMap()diciptakan sebagai steno. Ini menerapkan fungsi pemetaan seperti biasa map(), tetapi kemudian bukannya memancarkan nilai yang dikembalikan itu juga "meratakan" (atau menggabungkan) mereka.

Itu kasus penggunaan umum. Ini sangat berguna dalam basis kode yang menggunakan Rx allover tempat dan Anda punya banyak metode mengembalikan yang dapat diamati, yang Anda ingin rantai dengan metode lain yang mengembalikan diamati.

Dalam use case Anda, itu kebetulan juga berguna, karena map()hanya dapat mengubah satu nilai yang dipancarkan onNext()menjadi nilai lain yang dipancarkan onNext(). Tapi itu tidak bisa mengubahnya menjadi beberapa nilai, tidak ada nilai sama sekali atau kesalahan. Dan seperti yang ditulis akarnokd dalam jawabannya (dan pikiran Anda dia jauh lebih pintar dari saya, mungkin secara umum, tetapi setidaknya ketika datang ke RxJava) Anda tidak harus membuang pengecualian dari Anda map(). Jadi, alih-alih, Anda dapat menggunakan flatMap()dan

return Observable.just(value);

ketika semuanya berjalan dengan baik, tapi

return Observable.error(exception);

ketika sesuatu gagal.
Lihat jawabannya untuk cuplikan lengkap: https://stackoverflow.com/a/30330772/1402641

Marcin Koziński
sumber
1
ini jawaban favorit saya. Anda pada dasarnya bersarang di diamati JIKA diamati itu adalah apa metode Anda kembali.
filthy_wizard
21

Pertanyaannya adalah Kapan Anda menggunakan peta vs flatMap di RxJava? . Dan saya pikir demo sederhana lebih spesifik.

Saat Anda ingin mengonversi item yang dipancarkan ke jenis lain, dalam kasus Anda, mengonversi file ke String, map, dan flatMap dapat berfungsi. Tapi saya lebih suka operator peta karena lebih jelas.

Namun di beberapa tempat, flatMapbisa melakukan pekerjaan sihir tetapi maptidak bisa. Sebagai contoh, saya ingin mendapatkan info pengguna tetapi saya harus mendapatkan idnya terlebih dahulu ketika pengguna masuk. Jelas saya membutuhkan dua permintaan dan semuanya dalam rangka.

Mari kita mulai.

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

Observable<UserInfo> fetchUserInfo(String userId);

Berikut adalah dua metode, satu untuk masuk kembali Response, dan satu lagi untuk mengambil info pengguna.

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

Seperti yang Anda lihat, dalam fungsi flatMap berlaku, pada awalnya saya mendapatkan id pengguna dari Responsekemudian mengambil info pengguna. Ketika dua permintaan selesai, kita dapat melakukan pekerjaan kita seperti memperbarui UI atau menyimpan data ke dalam basis data.

Namun jika Anda menggunakan mapAnda tidak dapat menulis kode yang bagus. Singkatnya, flatMapdapat membantu kami membuat serialisasi permintaan.

KoXier
sumber
18

Berikut ini adalah sederhana thumb-aturan yang saya gunakan membantu saya memutuskan seperti ketika menggunakan flatMap()lebih map()di Rx Observable.

Setelah Anda mengambil keputusan bahwa Anda akan menggunakan maptransformasi, Anda akan menulis kode transformasi Anda untuk mengembalikan beberapa Objek, kan?

Jika yang Anda kembalikan sebagai hasil akhir dari transformasi Anda adalah:

  • objek yang tidak dapat diobservasi maka Anda hanya akan menggunakannyamap() . Dan map()membungkus objek itu dalam Observable dan memancarkannya.

  • sebuah Observableobjek, maka Anda akan gunakanflatMap() . Dan flatMap()membuka bungkus yang dapat diobservasi, mengambil objek yang dikembalikan, membungkusnya dengan yang dapat diamati sendiri dan memancarkannya.

Katakanlah misalnya kita memiliki metode titleCase (String inputParam) yang mengembalikan objek Titled Cased String dari param input. Jenis pengembalian metode ini bisa Stringatau Observable<String>.

  • Jika jenis pengembaliannya titleCase(..)hanya berupa String, maka Anda akan menggunakannyamap(s -> titleCase(s))

  • Jika jenis pengembaliannya titleCase(..)adalah Observable<String>, maka Anda akan menggunakanflatMap(s -> titleCase(s))

Harapan itu menjelaskan.

karthiks
sumber
11

Saya hanya ingin menambahkan bahwa dengan flatMap, Anda tidak benar-benar perlu menggunakan kustom Anda sendiri yang dapat diamati di dalam fungsi dan Anda dapat mengandalkan metode / operator pabrik standar:

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

Secara umum, Anda harus menghindari melempar (Runtime-) pengecualian dari metode onXXX dan callback jika memungkinkan, meskipun kami menempatkan perlindungan sebanyak mungkin di RxJava.

akarnokd
sumber
Tapi saya pikir peta sudah cukup. Jadi flatMap dan map adalah kebiasaan, kan?
CoXier
6

Dalam skenario yang menggunakan peta, Anda tidak perlu diamati untuk itu.

Anda harus menggunakan exception.propagate, yang merupakan pembungkus sehingga Anda dapat mengirim pengecualian yang diperiksa ke mekanisme 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
        } 
    } 
});

Anda kemudian harus menangani kesalahan ini di pelanggan

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

Ada posting yang sangat bagus untuk itu: http://blog.danlew.net/2015/12/12/error-handling-in-rxjava/

ndori
sumber
0

Dalam beberapa kasus Anda mungkin akhirnya memiliki rantai yang dapat diobservasi, di mana observable Anda akan mengembalikan observable lain. jenis 'flatmap' membuka bungkusan yang dapat diamati kedua yang terkubur di yang pertama dan membiarkan Anda langsung mengakses data yang diamati kedua diludahkan saat berlangganan.

Anoop Isaac
sumber
0

Flatmap memetakan yang bisa diamati ke yang bisa diamati. Peta item peta ke item.

Flatmap lebih fleksibel tetapi Map lebih ringan dan langsung, jadi itu tergantung pada penggunaan Anda.

Jika Anda melakukan APA SAJA async (termasuk mengganti utas), Anda harus menggunakan Flatmap, karena Map tidak akan memeriksa apakah konsumen dibuang (bagian dari yang ringan)

skr1p7k1dd
sumber