[javascript] Why do we need to use flatMap?

I am starting to use RxJS and I don't understand why in this example we need to use a function like flatMap or concatAll; where is the array of arrays here?

var requestStream = Rx.Observable.just('https://api.github.com/users');

var responseMetastream = requestStream
  .flatMap(function(requestUrl) {
    return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
  });

responseMetastream.subscribe(url => {console.log(url)})

If someone can visually explain what is happening, it will be very helpful.

This question is related to javascript rxjs

The answer is


With flatMap

var requestStream = Rx.Observable.just('https://api.github.com/users');

var responseMetastream = requestStream
  .flatMap(function(requestUrl) {
    return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
  });

responseMetastream.subscribe(json => {console.log(json)})

Without flatMap

var requestStream = Rx.Observable.just('https://api.github.com/users');

var responseMetastream = requestStream
  .map(function(requestUrl) {
    return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
  });

responseMetastream.subscribe(jsonStream => {
  jsonStream.subscribe(json => {console.log(json)})
})

An Observable is an object that emits a stream of events: Next, Error and Completed.

When your function returns an Observable, it is not returning a stream, but an instance of Observable. The flatMap operator simply maps that instance to a stream.

That is the behaviour of flatMap when compared to map: Execute the given function and flatten the resulting object into a stream.


It's not an array of arrays. It's an observable of observable(s).

The following returns an observable stream of string.

requestStream
  .map(function(requestUrl) {
    return requestUrl;
  });

While this returns an observable stream of observable stream of json

requestStream
  .map(function(requestUrl) {
    return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
  });

flatMap flattens the observable automatically for us so we can observe the json stream directly


People tend to over complicate things by giving the definition which says:

flatMap transform the items emitted by an Observable into Observables, then flatten the emissions from those into a single Observable

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 Situation: we have an observable which returns data(simple URL) that we are going to use to make an HTTP call that will return an observable containing the data we need so you 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 the first way 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));

Simple:

[1,2,3].map(x => [x, x * 10])
// [[1, 10], [2, 20], [3, 30]]

[1,2,3].flatMap(x => [x, x * 10])
// [1, 10, 2, 20, 3, 30]]

flatMap transform the items emitted by an Observable into new Observables, then flattens the emissions from those into a single Observable.

Check out the scenario below where get("posts") returns an Observable that is "flattened" by flatMap.

myObservable.map(e => get("posts")).subscribe(o => console.log(o));
// this would log Observable objects to console.  

myObservable.flatMap(e => get("posts")).subscribe(o => console.log(o));
// this would log posts to console.

Here to show equivalent implementation of a flatMap using subscribes.

Without flatMap:

this.searchField.valueChanges.debounceTime(400)
.subscribe(
  term => this.searchService.search(term)
  .subscribe( results => {
      console.log(results);  
      this.result = results;
    }
  );
);

With flatMap:

this.searchField.valueChanges.debounceTime(400)
    .flatMap(term => this.searchService.search(term))
    .subscribe(results => {
      console.log(results);
      this.result = results;
    });

http://plnkr.co/edit/BHGmEcdS5eQGX703eRRE?p=preview

Hope it could help.

Olivier.


['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.

_x000D_
_x000D_
var source = Rx.Observable.interval(100).take(10).map(function(num){_x000D_
    return num+1_x000D_
});_x000D_
source.subscribe(function(e){_x000D_
    console.log(e)_x000D_
})
_x000D_
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.4.1/Rx.min.js"></script>
_x000D_
_x000D_
_x000D_

_x000D_
_x000D_
var source = Rx.Observable.interval(100).take(10).flatMap(function(num){_x000D_
    return Rx.Observable.timer(100).map(() => num)_x000D_
});_x000D_
source.subscribe(function(e){_x000D_
    console.log(e)_x000D_
})
_x000D_
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.4.1/Rx.min.js"></script>
_x000D_
_x000D_
_x000D_


flatMap transform the items emitted by an Observable into Observables, then flatten the emissions from those into a single Observable

I am not stupid but had to read this 10 times and still don't get it. When I read the code snippet :

[1,2,3].map(x => [x, x * 10])
// [[1, 10], [2, 20], [3, 30]]

[1,2,3].flatMap(x => [x, x * 10])
// [1, 10, 2, 20, 3, 30]

then I could understand whats happening, it does two things :

flatMap :

  1. map: transform *) emitted items into Observables.
  2. flat: then merge those Observables as one Observable.

*) The transform word says the item can be transformed in something else.

Then the merge operator becomes clear to, it does the flattening without the mapping. Why not calling it mergeMap? It seems there is also an Alias mergeMap with that name for flatMap.