+16

RxJava: Sự khác biệt giữa Flatmap, SwitchMap, ConcatMap

Chào mọi người, chắc hẳn khi các bạn sử dụng Rx đều biết đến một số các phương thức để chuyển đổi từ Observable dạng này sang một Observable dạng khác, mà phương thức đầu tiên ta biết hẳn là FlatMap. Nhưng ngoài ra, Rx còn cung cấp cho ta một số các phương thức khác như SwitchMap, ConcatMap

Vậy chúng có gì khác biệt và được dùng trong trường hợp nào, hãy cùng tìm hiểu nhé!

FlatMap

Đầu tiên hãy bắt đầu với một đoạn Java Unit test để chạy thử flatMap. Đầu tiên ta tạo ra một list String, sau đó sẽ chuyển đổi mỗi phần tử trong list đó thành một Observable sử dụng operator from. Mỗi phần tử sau khi được map vào Observable sẽ được cộng thêm với chữ "x" ở cuối. Tiếp theo mỗi Observable sẽ delay một khoảng random từ 0 - 10s. Cuối cùng ta cộng thêm thời gian chạy 1 phút để chắc chắn tất cả các phần tử đã đượt phát ra(emit). (Nếu không làm vậy, bài test có thể kết thúc trước khi toàn bộ các phần tử được emit.)

Chuyển sang code:

@Test
public void flatMap() throws Exception {
    final List<String> items = Lists.newArrayList("a", "b", "c", "d", "e", "f");

    final TestScheduler scheduler = new TestScheduler();

    Observable.from(items)
            .flatMap( s -> {
                final int delay = new Random().nextInt(10);
                return Observable.just(s + "x")
                        .delay(delay, TimeUnit.SECONDS, scheduler);
            })
            .toList()
            .doOnNext(System.out::println)
            .subscribe();

    scheduler.advanceTimeBy(1, TimeUnit.MINUTES);
}

Sau khi chạy thử, kết quả là [cx, ex, fx, bx, dx, ax]

Ồ, bạn có thấy gì lạ không, các phần tử của list sau khi được flatMapped thì đã không theo thứ tự như trước nữa. Hãy đọc thử phần docs của FlatMap bạn sẽ rõ:

Note that FlatMap merges the emissions of these Observables, so that they may interleave.

Mình tạm giải thích đó là Operator flatMap sẽ không quan tâm đến thứ tự của các phần tử. Nó sẽ tạo một Observable mới cho mỗi phần tử và không liên quan gì đến nhau. Có phần tử sẽ emit nhanh, có phần tử emit chậm bởi vì trước đó mình đã tạo một đoạn delay ngẫu nhiên cho các phần tử.

SwitchMap

Với cùng bài test trên, mọi thứ được giữ nguyên, chỉ thay đổi Operator flatMap thành switchMap và xem điều gì xảy ra nhé:

@Test
public void switchMap() throws Exception {
    final List<String> items = Lists.newArrayList("a", "b", "c", "d", "e", "f");

    final TestScheduler scheduler = new TestScheduler();

    Observable.from(items)
            .switchMap( s -> {
                final int delay = new Random().nextInt(10);
                return Observable.just(s + "x")
                        .delay(delay, TimeUnit.SECONDS, scheduler);
            })
            .toList()
            .doOnNext(System.out::println)
            .subscribe();

    scheduler.advanceTimeBy(1, TimeUnit.MINUTES);
}

Kết quả: [fx]

Xem kết quả bạn cũng hiểu rồi đúng không 😄 Nôm na là khi một phần tử mới được emit, thì nó sẽ huỷ (unsubcribe) Observable được tạo ra trước đó và sẽ chạy Observable mới.

Để rõ hơn bạn có thể đọc đoạn document

whenever a new item is emitted by the source Observable, it will unsubscribe to and stop mirroring the Observable that was generated from the previously-emitted item, and begin only mirroring the current one.

ConcatMap

Tương tự, thay thế bằng operator concatMap

@Test
public void switchMap() throws Exception {
    final List<String> items = Lists.newArrayList("a", "b", "c", "d", "e", "f");

    final TestScheduler scheduler = new TestScheduler();

    Observable.from(items)
            .concatMap( s -> {
                final int delay = new Random().nextInt(10);
                return Observable.just(s + "x")
                        .delay(delay, TimeUnit.SECONDS, scheduler);
            })
            .toList()
            .doOnNext(System.out::println)
            .subscribe();

    scheduler.advanceTimeBy(1, TimeUnit.MINUTES);

Kết quả: [ax, bx, cx, dx, ex, fx]

ConcatMap hoạt động gần giống với flatMap, nhưng thứ tự của các phần tử sau khi emit được giữ lại như trước. Nhưng concatMap có một vấn đề lớn đó là nó sẽ chờ cho mỗi observable hoàn thành xong công việc rồi mới chạy đến phần tử tiếp theo (Kiểu như synchronus vậy).

Để rõ hơn thì ta sẽ test thử bằng đoạn ví dụ sau:

@Test
public void flatMapAndConcatMapCompare() throws Exception {
    final List<String> items = Lists.newArrayList("a", "b", "c", "d", "e", "f");

    final TestScheduler scheduler1 = new TestScheduler();
    final TestScheduler scheduler2 = new TestScheduler();

    Observable.from(items)
            .flatMap(s -> Observable.just(s + "x")
                    .delay(5, TimeUnit.SECONDS, scheduler1)
                    .doOnNext(str -> System.out.print(scheduler1.now() + " ")))
            .toList()
            .doOnNext(strings -> System.out.println("\nEND:" + scheduler1.now()))
            .subscribe();

    scheduler1.advanceTimeBy(1, TimeUnit.MINUTES);

    Observable.from(items)
            .concatMap(s -> Observable.just(s + "x")
                    .delay(5, TimeUnit.SECONDS, scheduler2)
                    .doOnNext(str -> System.out.print(scheduler2.now() + " ")))
            .toList()
            .doOnNext(strings -> System.out.println("\nEND:" + scheduler2.now()))
            .subscribe();

    scheduler2.advanceTimeBy(1, TimeUnit.MINUTES);

}

Kết quả:

5000 5000 5000 5000 5000 5000 
END:5000
5000 10000 15000 20000 25000 30000 
END:30000

Đoạn test trên chỉ ra rằng mặc dù concatMap rất là thích hợp để sử dụng, nhưng cần chú ý là nó sẽ phá vỡ cơ chế bất đồng bộ(asynchonus) và làm cho toàn bộ quá trình chạy chậm hơn,

Bonus: ngoài concatMap bạn có thể chú ý hơn đến một operator nữa là concatMapEager, mình giải thích đơn giản là nó sẽ giống như concatMap, thứ tự của các phần tử sau khi emit được giữ nguyên, tuy nhiên quá trình chạy mỗi phần tử là song song như flatMap, và nó sẽ chờ cho tất cả hoàn thành rồi sắp xếp lại thứ tự của các phần tử. Nó sẽ khắc phục được nhược điểm của concatMap.

Sử dụng Operator nào?

Chắc bạn cũng đã hiểu được sự khác nhau giữa 3 operators trên, tuy nhiên khi ứng dụng vào thực tế thì ta sẽ chọn cái nào? Mình sẽ liệt kê một số các case thường hay sử dụng cho mỗi operator trên nhé

  1. Xử lý dữ liệu mới(Ví dụ như list các bài post trên new feed)

    Sử dụng switchMap là hợp lý bởi vì ta không cần quan tâm tới các dữ liệu cũ nữa. Vì thế khá là an toàn khi ta có thể huỷ nó và tập trung vào những dữ liệu mới nhất. Nó sẽ tiết kiệm được chút thời gian khi mà phần xử lý cũ đã được bỏ qua.

  2. Lấy một dữ liệu cụ thể từ mỗi item trong một list (Ví dụ như lấy avatar của mỗi người trong danh bạ)

    Trong trường hợp này nên sử dụng concatMap. Bởi vì nếu dùng flatMap thì sẽ dẫn tới tình trạng xáo trộn thứ tự của các avatars -> Hiển thị sai. switchMap thì loại luôn bởi vì ta cần lấy tất cả avatar.

  3. Làm gì đó với mỗi item trong một mảng đã được sắp xếp:

    Chắc bạn cũng đã biết phải chọn gì rồi đó, concatMap là lựa chọn của mình. Hihi

  4. Gửi một thông tin tới tất cả các item trong list(Vd: gửi một tin nhắn tới tất cả mọi người trong danh sách)

    Để chắc chắn tất cả các request được gọi, ta sẽ không sử dụng switchMap ở đây, và flatMapconcatMap đều có thể làm được việc này, nhưng flatMap sẽ tốt hơn bởi vì ta không cần quan tâm tới sự sắp xếp theo thứ tự và ta có thể gọi tất cả các request đồng thời để nhận kết quả được nhanh hơn.

  5. Tìm kiếm các items bởi đoạn query

    Ví dụ người dùng nhập chữ 'x' sau đó đến chữ 'y'. Thì phần query sẽ là 'xy' và ta sẽ không cần quan tâm đến kết quả khi câu query là chữ 'x' nữa. Vì thế hãy thoải mái mà chọn switchMap thôi!

Tổng kết

flatMap: không quan tâm tới sự sắp xếp của items, chạy bất đồng bộ.

switchMap: unsubcribe observable trước đó sau khi emit một cái mới

concatMap: giữ nguyên sắp xếp các items, chạy đồng bộ.

Cảm ơn các bạn đã theo dõi bài viết, mong bài viết sẽ giúp bạn hiểu rõ hơn về 3 operator này và sử dụng nó vào đúng mục đích.

Bài gốc: https://medium.com/appunite-edu-collection/rxjava-flatmap-switchmap-and-concatmap-differences-examples-6d1f3ff88ee0


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí