Why do we need to use flatMap?
['a','b','c'].flatMap(function(e) { return [e, e+ 'x', e+ 'y', e+ 'z' ];});//['a', 'ax', 'ay', 'az', 'b', 'bx', 'by', 'bz', 'c', 'cx', 'cy', 'cz']['a','b','c'].map(function(e) { return [e, e+ 'x', e+ 'y', e+ 'z' ];});//[Array[4], Array[4], Array[4]]
You use flatMap when you have an Observable whose results are more Observables.
If you have an observable which is produced by an another observable you can not filter, reduce, or map it directly because you have an Observable not the data. If you produce an observable choose flatMap over map; then you are okay.
As in second snippet, if you are doing async operation you need to use flatMap.
var source = Rx.Observable.interval(100).take(10).map(function(num){ return num+1});source.subscribe(function(e){ console.log(e)})
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.4.1/Rx.min.js"></script>
var source = Rx.Observable.interval(100).take(10).flatMap(function(num){ return Rx.Observable.timer(100).map(() => num)});source.subscribe(function(e){ console.log(e)})
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.4.1/Rx.min.js"></script>
When I started to have a look at Rxjs
I also stumbled on that stone. What helped me is the following:
- documentation from reactivex.io . For instance, for
flatMap
: http://reactivex.io/documentation/operators/flatmap.html - documentation from rxmarbles : http://rxmarbles.com/. You will not find
flatMap
there, you must look atmergeMap
instead (another name). - the introduction to Rx that you have been missing: https://gist.github.com/staltz/868e7e9bc2a7b8c1f754. It addresses a very similar example. In particular it addresses the fact that a promise is akin to an observable emitting only one value.
finally looking at the type information from RxJava. Javascript not being typed does not help here. Basically if
Observable<T>
denotes an observable object which pushes values of type T, thenflatMap
takes a function of typeT' -> Observable<T>
as its argument, and returnsObservable<T>
.map
takes a function of typeT' -> T
and returnsObservable<T>
.Going back to your example, you have a function which produces promises from an url string. So
T' : string
, andT : promise
. And from what we said beforepromise : Observable<T''>
, soT : Observable<T''>
, withT'' : html
. If you put that promise producing function inmap
, you getObservable<Observable<T''>>
when what you want isObservable<T''>
: you want the observable to emit thehtml
values.flatMap
is called like that because it flattens (removes an observable layer) the result frommap
. Depending on your background, this might be chinese to you, but everything became crystal clear to me with typing info and the drawing from here: http://reactivex.io/documentation/operators/flatmap.html.
People tend to over complicate things by giving the definition which says:
flatMap transform the items emitted by an Observable intoObservables, then flatten the emissions from those into a singleObservable
I swear this definition still confuses me but I am going to explain it in the simplest way which is by using an example
Our Simple Example
1- We have an observable which returns a simple URL string.
2- We have to use that URL to make a second HTTP call.
3- The second HTTP call will return an observable containing the data we need.
So we can visualize the situation like this:
Observable 1 |_ Make Http Call Using Observable 1 Data (returns Observable_2) |_ The Data We Need
so as you can see we can't reach the data we need directly 🤔
so to retrieve the data we can use just normal subscriptions like this:
Observable_1.subscribe((URL) => { Http.get(URL).subscribe((Data_We_Need) => { console.log(Data_We_Need); });});
this works but as you can see we have to nest subscriptions to get our data this currently does not look bad but imagine we have 10 nested subscriptions that would become unmaintainable!
so a better way to handle this is just to use the operator flatMap
which will do the same thing but makes us avoid that nested subscription:
Observable_1 .flatMap(URL => Http.get(URL)) .subscribe(Data_We_Need => console.log(Data_We_Need));