RxJava에서 언제 map vs flatMap을 사용합니까?
RxJava에서 언제 map vs flatMap을 사용합니까?
예를 들어 JSON이 포함 된 파일을 JSON이 포함 된 문자열에 매핑하려고합니다.
map을 사용하면 어떻게 든 예외를 처리해야합니다. 그러나 어떻게? :
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을 사용하면 훨씬 더 장황하지만 Observables 체인으로 문제를 전달하고 다른 곳을 선택하고 다시 시도하면 오류를 처리 할 수 있습니다.
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);
}
}
});
}
});
나는 맵의 단순함을 좋아하지만 플랫 맵의 오류 처리 (자세한 것은 아님). 나는 떠 다니는 것에 대한 모범 사례를 보지 못했고 이것이 실제로 어떻게 사용되고 있는지 궁금합니다.
map
한 이벤트를 다른 이벤트로 변환하십시오. flatMap
하나의 이벤트를 0 개 이상의 이벤트로 변환합니다. (이것은 IntroToRx 에서 가져온 것입니다 )
json을 객체로 변환하려면 map을 사용하는 것으로 충분합니다.
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을 반환 할 수도 있습니다.
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 Apply 함수는 연결된 응답을 통해 변환을 수행합니다 (Observable을 반환하지 않음). FlatMap apply 함수는을 반환하는 반면, Observable<T>
메소드 내에서 비동기 호출을 수행하려는 경우 FlatMap이 권장됩니다.
요약:
- 지도는 T 유형의 객체를 반환합니다
- FlatMap은 Observable을 반환합니다.
http://blog.couchbase.com/why-couchbase-chose-rxjava-new-java-sdk 에서 명확한 예를 볼 수 있습니다 .
Couchbase Java 2.X Client는 Rx를 사용하여 편리한 방식으로 비동기 호출을 제공합니다. Rx를 사용하기 때문에 메소드 맵과 FlatMap이 있으므로 문서의 설명이 일반적인 개념을 이해하는 데 도움이 될 수 있습니다.
오류를 처리하려면 susbcriber에서 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 function simply accepts an item and returns an item which will be emitted further (only once) down.
flatMap - supplied function accepts an item then returns an "Observable", meaning each item of the new "Observable" will be emitted separately further down.
May be code will clear things up for you:
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);
Output:
inside the map item1
item1
inside the flatMap item2
item2+
item2++
item2+++
The way I think about it is that you use flatMap
when the function you wanted to put inside of map()
returns an Observable
. In which case you might still try to use map()
but it would be unpractical. Let me try to explain why.
If in such case you decided to stick with map
, you would get an Observable<Observable<Something>>
. For example in your case, if we used an imaginary RxGson library, that returned an Observable<String>
from it's toJson()
method (instead of simply returning a String
) it would look like this:
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
At this point it would be pretty tricky to subscribe()
to such an observable. Inside of it you would get an Observable<String>
to which you would again need to subscribe()
to get the value. Which is not practical or nice to look at.
So to make it useful one idea is to "flatten" this observable of observables (you might start to see where the name _flat_Map comes from). RxJava provides a few ways to flatten observables and for sake of simplicity lets assume merge is what we want. Merge basically takes a bunch of observables and emits whenever any of them emits. (Lots of people would argue switch would be a better default. But if you're emitting just one value, it doesn't matter anyway.)
So amending our previous snippet we would get:
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
This is a lot more useful, because subscribing to that (or mapping, or filtering, or...) you just get the String
value. (Also, mind you, such variant of merge()
does not exist in RxJava, but if you understand the idea of merge then I hope you also understand how that would work.)
So basically because such merge()
should probably only ever be useful when it succeeds a map()
returning an observable and so you don't have to type this over and over again, flatMap()
was created as a shorthand. It applies the mapping function just as a normal map()
would, but later instead of emitting the returned values it also "flattens" (or merges) them.
That's the general use case. It is most useful in a codebase that uses Rx allover the place and you've got many methods returning observables, which you want to chain with other methods returning observables.
In your use case it happens to be useful as well, because map()
can only transform one value emitted in onNext()
into another value emitted in onNext()
. But it cannot transform it into multiple values, no value at all or an error. And as akarnokd wrote in his answer (and mind you he's much smarter than me, probably in general, but at least when it comes to RxJava) you shouldn't throw exceptions from your map()
. So instead you can use flatMap()
and
return Observable.just(value);
when all goes well, but
return Observable.error(exception);
when something fails.
See his answer for a complete snippet: https://stackoverflow.com/a/30330772/1402641
Here is a simple thumb-rule that I use help me decide as when to use flatMap()
over map()
in Rx's Observable
.
Once you come to a decision that you're going to employ a map
transformation, you'd write your transformation code to return some Object right?
If what you're returning as end result of your transformation is:
a non-observable object then you'd use just
map()
. Andmap()
wraps that object in an Observable and emits it.an
Observable
object, then you'd useflatMap()
. AndflatMap()
unwraps the Observable, picks the returned object, wraps it with its own Observable and emits it.
Say for example we've a method titleCase(String inputParam) that returns Titled Cased String object of the input param. The return type of this method can be String
or Observable<String>
.
If the return type of
titleCase(..)
were to be mereString
, then you'd usemap(s -> titleCase(s))
If the return type of
titleCase(..)
were to beObservable<String>
, then you'd useflatMap(s -> titleCase(s))
Hope that clarifies.
The question is When do you use map vs flatMap in RxJava?. And I think a simple demo is more specific.
When you want to convert item emitted to another type , in your case converting file to String, map and flatMap can both work. But I prefer map operator because it's more clearly.
However in some place, flatMap
can do magic work but map
can't. For example, I want to get a user's info but I have to first get his id when user login in. Obviously I need two requests and they are in order.
Let's begin.
Observable<LoginResponse> login(String email, String password);
Observable<UserInfo> fetchUserInfo(String userId);
Here are two methods, one for login returned Response
, and another for fetching user info.
login(email, password)
.flatMap(response ->
fetchUserInfo(response.id))
.subscribe(userInfo -> {
// get user info and you update ui now
});
As you see, in function flatMap applies, at first I get user id from Response
then fetch user info. When two requests are finished, we can do our job such as updating UI or save data into database.
However if you use map
you can't write such nice code. In a word, flatMap
can help us serialize requests.
I just wanted to add that with flatMap
, you don't really need to use your own custom Observable inside the function and you can rely on standard factory methods/operators:
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);
}
}
});
Generally, you should avoid throwing (Runtime-) exceptions from onXXX methods and callbacks if possible, even though we placed as many safeguards as we could in RxJava.
In that scenario use map, you don't need a new Observable for it.
you should use Exceptions.propagate, which is a wrapper so you can send those checked exceptions to the rx mechanism
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
}
}
});
You then should handle this error in the subscriber
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 }
};);
There is an excellent post for it: http://blog.danlew.net/2015/12/08/error-handling-in-rxjava/
In some cases you might end up having chain of observables, wherein your observable would return another observable. 'flatmap' kind of unwraps the second observable which is buried in the first one and let you directly access the data second observable is spitting out while subscribing.
Flatmap maps observables to observables. Map maps items to items.
Flatmap is more flexible but Map is more lightweight and direct, so it kind of depends on your usecase.
If you are doing ANYTHING async (including switching threads), you should be using Flatmap, as Map will not check if the consumer is disposed (part of the lightweight-ness)
참고URL : https://stackoverflow.com/questions/22847105/when-do-you-use-map-vs-flatmap-in-rxjava
'development' 카테고리의 다른 글
변경할 때 UITableView에 대한 reloadData에 애니메이션을 적용하십시오. (0) | 2020.05.26 |
---|---|
C 또는 C ++에서 문자열을 어떻게 뒤집습니까? (0) | 2020.05.26 |
$ routeProvider와 $ stateProvider의 차이점은 무엇입니까? (0) | 2020.05.25 |
Linux 공유 라이브러리가 내보내는 함수 목록을 보려면 어떻게합니까? (0) | 2020.05.25 |
비 활동 클래스 (LocationManager)에서 getSystemService를 사용하려면 어떻게해야합니까? (0) | 2020.05.25 |