Когда вы используете map vs flatMap в RxJava?



когда вы используете map vs flatMap в RxJava?



скажем, например, мы хотим сопоставить файлы, содержащие JSON, в строки, содержащие JSON--



используя карту, мы должны как-то справиться с исключением. Но как?:



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


используя flatMap, это гораздо более подробно, но мы можем направить проблему вниз по цепочке наблюдаемых и обработать ошибку, если мы выберем где-то еще и даже повторим:



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


мне нравится простота карты, но обработка ошибок flatmap (не многословие). Я не видел никаких лучших практик по этому плаванию, и мне любопытно, как это используется на практике.

719   9  

9 ответов:

map преобразование одного события к другому. flatMap преобразование одного события в ноль или более событий. (это взято из IntroToRx)

как вы хотите, чтобы преобразовать json в объект, используя карту должно быть достаточно.

работа с FileNotFoundException-это еще одна проблема (использование map или flatmap не решит эту проблему).

чтобы решить вашу проблему исключения, просто бросьте его с непроверенным исключением : RX вызовет обработчик onError для тебя.

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

точно такая же версия с 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
        }
    }
});

вы также можете вернуть в версии 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) {
            return Observable.error(OnErrorThrowable.addValueAsLastCause(e, file));
        }
    }
});

FlatMap ведет себя очень похоже на карту, разница в том, что функция, которую он применяет возвращает сам наблюдаемый, поэтому он идеально подходит для сопоставления асинхронных операций.

в практическом смысле функция Map applies просто выполняет преобразование над цепным ответом (не возвращая наблюдаемый); в то время как функция FlatMap applies возвращает Observable<T>, именно поэтому FlatMap рекомендуется, если вы планируете сделать асинхронный вызов внутри метод.

резюме:

  • карта возвращает объект типа T
  • FlatMap возвращает наблюдаемый.

наглядный пример можно увидеть здесь:http://blog.couchbase.com/why-couchbase-chose-rxjava-new-java-sdk .

Couchbase Java 2.Клиент X использует Rx для обеспечения асинхронных вызовов удобным способом. Поскольку он использует Rx, у него есть методы map и FlatMap, объяснение в их документации может быть полезно понять общую концепцию.

для обработки ошибок, переопределить метод onerror на ваш абонента.

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

Это может помочь взглянуть на этот документ: http://blog.danlew.net/2014/09/15/grokking-rxjava-part-1/

хороший источник о том, как управлять ошибками с RX можно найти по адресу:https://gist.github.com/daschl/db9fcc9d2b932115b679

в вашем случае я думаю, что вам нужна карта, поскольку там только 1 вход и 1 выход.

функция map-supplied просто принимает элемент и возвращает элемент, который будет излучаться дальше (только один раз) вниз.

функция flatMap-supplied принимает элемент, а затем возвращает "Observable", то есть каждый элемент нового "Observable" будет излучаться отдельно дальше вниз.

может быть код прояснит вещи для вы.

        //START DIFFERENCE BETWEEN MAP AND FLATMAP
    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);
    //END DIFFERENCE BETWEEN MAP AND FLATMAP

выход:

inside the map item1
item1
inside the flatMap item2
item2+
item2++
item2+++

то, как я думаю об этом, что вы используете flatMap когда функция, которую вы хотели поставить внутри map() возвращает Observable. В этом случае вы все равно можете попробовать использовать map() но это было бы непрактично. Позвольте мне попытаться объяснить, почему.

если в таком случае вы решили придерживаться map, вы получите Observable<Observable<Something>>. Например, в вашем случае, если мы использовали воображаемую библиотеку RxGson, которая вернула Observable<String> С toJson() метод (вместо простого возврата a 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

в этот момент было бы довольно сложно subscribe() к такому наблюдаемому. Внутри него вы получите Observable<String> к которому вам снова нужно будет subscribe() чтобы получить значение. Что не практично и не приятно смотреть.

слияние это то, что мы хотим. Слияние в основном принимает кучу наблюдаемых и испускает всякий раз, когда какой-либо из них испускает. (Многие люди будут спорить переключатель было бы лучше по умолчанию. Но если вы излучаете только одно значение, это не имеет значения в любом случае.)

Итак, изменив наш предыдущий фрагмент, мы получим:

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

это намного полезнее, потому что подписка на это (или отображение, или фильтрация, или...) ты просто получить String значение. (Также, заметьте, такой вариант merge() не существует в RxJava, но если вы понимаете идею слияния, то я надеюсь, вы также понимаете, как это будет работать.)

так в основном потому, что такие merge() вероятно, должен быть полезен только тогда, когда он преуспевает в map() возвращая наблюдаемый и поэтому вам не нужно вводить это снова и снова, flatMap() был создан как стенография. Он применяет функцию отображения так же, как обычный map() будет, но позже вместо того, чтобы излучать возвращенные значения, он также "сглаживает" (или сливает) их.

это общий случай использования. Это наиболее полезно в кодовой базе, которая использует Rx по всему месту, и у вас есть много методов, возвращающих наблюдаемые, которые вы хотите связать с другими методами, возвращающими наблюдаемые.

в вашем случае использования это также полезно, потому что map() можно преобразовать только одно значение, испускаемое в onNext() в другое значение, выбрасываемых в onNext(). Но это не удается преобразовать его в несколько значений, нет значения вообще или ошибка. И как akarnokd написал в своем ответе (и имейте в виду, что он намного умнее меня, вероятно, в целом, но, по крайней мере, когда дело доходит до RxJava) вы не должны бросать исключения из своего map(). Так что вместо этого вы можете использовать flatMap() и

return Observable.just(value);

когда все идет хорошо, но

return Observable.error(exception);

когда что-то не удается.
См. его ответ для полного фрагмента: https://stackoverflow.com/a/30330772/1402641

вот простой thumb-rule что я использую помогите мне решить, как когда использовать flatMap() over map() в Rx Observable.

как только вы придете к решению, что вы собираетесь использовать map преобразование, вы бы написали свой код преобразования, чтобы вернуть какой-то объект правильно?

если то, что вы возвращаетесь в качестве конечного результата преобразования:

  • ненаблюдаемый объект, то вы бы использовали просто map(). И map() обертывает этот объект в наблюдаемый и испускает его.

  • an Observable объект, то вы бы использовали flatMap(). И flatMap() разворачивает наблюдаемое, выбирает возвращаемый объект, обертывает его своим собственным наблюдаемым и испускает его.

скажем, например, у нас есть метод titleCase(String inputParam), который возвращает объект Cased String входного параметра. Возвращаемый тип этого метода может быть String или Observable<String>.

  • если возвращаемый тип titleCase(..) должны были быть просто String, тогда вы могли бы использовать map(s -> titleCase(s))

  • если возвращаемый тип titleCase(..) должны быть Observable<String>, тогда вы могли бы использовать flatMap(s -> titleCase(s))

надеюсь, что это проясняет.

Я просто хотел добавить, что с flatMap, вам действительно не нужно использовать свой собственный пользовательский наблюдаемый внутри функции, и вы можете полагаться на стандартные заводские методы / операторы:

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

Как правило, вы должны избегать исключения (Runtime-) из методов onXXX и обратных вызовов, если это возможно, даже если мы разместили столько гарантий, сколько могли в RxJava.

вопрос когда вы используете map vs flatMap в RxJava?. И я думаю, что простая демонстрация более конкретна.

если вы хотите конвертировать элемент, выделенный в другой тип, в вашем случае преобразование файла в строку, карту и flatMap может работать. Но я предпочитаю оператор карты, потому что это более четко.

в каком-то месте, flatMap может делать магическую работу, но map не могу. Например, я хочу получить пользовательские данные, но мне нужно сначала получить его ID, когда пользователь войдите в систему. Очевидно, мне нужно два запроса и они в порядке.

давайте начнем.

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

Observable<UserInfo> fetchUserInfo(String userId);

вот два метода, один для входа возвращается Response, и еще один для получения информации о пользователе.

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

как вы видите, в функции flatMap применяется, сначала я получаю идентификатор пользователя от получить информацию о пользователе. Когда два запроса завершены, мы можем выполнить нашу работу, такую как обновление пользовательского интерфейса или сохранение данных в базе данных.

если вы используете map вы не можете напишите такой хороший код. Одним словом, flatMap может помочь нам сериализовать запросы.

в этом сценарии используйте карту, вам не нужен новый наблюдаемый для нее.

вы должны использовать исключения.propagate, который является оболочкой, поэтому вы можете отправить эти проверенные исключения в механизм 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
    } 
} 
});

затем вы должны обработать эту ошибку в абонента

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

есть отличный пост для него:http://blog.danlew.net/2015/12/08/error-handling-in-rxjava/

в некоторых случаях вы можете получить цепочку наблюдаемых, в которой ваш наблюдаемый вернет другой наблюдаемый. вид "flatmap" разворачивает второй наблюдаемый, который похоронен в первом, и позволяет вам напрямую обращаться к данным, которые второй наблюдаемый выплевывает во время подписки.

Comments

    Ничего не найдено.