108

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.

msanford
  • 11,803
  • 11
  • 66
  • 93
user233232
  • 6,067
  • 9
  • 29
  • 37
  • 1
    this answer is great because of the valuable references that it provides, but rxjs terminology doesn't translate well into English. (pictures are better). That's why I recommend instead running simple examples like this one, or more complex examples in the rxjs repo and adding ".do" operators before and after a flatmap and map operator, then just setting a breakpoint with the Chrome debugger. you'll see instantly that each produces a different output – HipsterZipster Jun 03 '16 at 23:06
  • 10
    I think if `flatMap` would have been named `mapThenFlatten`, then it would be less confusing. – goat Oct 24 '17 at 17:57
  • I personally don't like the example. Why would you subscribe to a url string observable. Of course it makes you think the 'rx' way, but in terms of code I don't find intuitive, unless you use it long enough so you don't question it anymore. But looks like a lot of overkill to me. No wonder people have trouble understanding. – html_programmer Oct 28 '21 at 00:11

11 Answers11

138
['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>
serkan
  • 6,885
  • 4
  • 41
  • 49
83

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 at mergeMap 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, then flatMap takes a function of type T' -> Observable<T> as its argument, and returns Observable<T>. map takes a function of type T' -> T and returns Observable<T>.

    Going back to your example, you have a function which produces promises from an url string. So T' : string, and T : promise. And from what we said before promise : Observable<T''>, so T : Observable<T''>, with T'' : html. If you put that promise producing function in map, you get Observable<Observable<T''>> when what you want is Observable<T''>: you want the observable to emit the html values. flatMap is called like that because it flattens (removes an observable layer) the result from map. 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.

Yennefer
  • 5,704
  • 7
  • 31
  • 44
user3743222
  • 18,345
  • 5
  • 69
  • 75
  • 2
    I forgot to mention that you should be able to simplify `return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));` to `return jQuery.getJSON(requestUrl);` as `flatMap` also accepts a selector function which returns a promise i.e. function of type `T' -> Promise`. – user3743222 Nov 02 '15 at 17:47
  • 3
    Wow, that GitHub Gist (https://gist.github.com/staltz/868e7e9bc2a7b8c1f754) is bloody fantastic. I recommend it to anyone working with any ReactiveX libraries like RxJS. – Jacob Stamm Jan 30 '18 at 21:33
  • @JacobStamm i agree . Just makes things easier . – CruelEngine Apr 13 '19 at 06:53
  • What does this syntax mean: `T’ -> T`? I understand the `T` as a generic, but what is the apostrophe and not-fat arrow? – 1252748 Dec 13 '19 at 14:33
  • You can replace T' by X or Y without changing the meaning anywhere in the answer. The arrow is Haskell notation for type signature. So T' -> T is the signature for a function which takes an element of type T' and returns an element of type T – user3743222 Dec 13 '19 at 17:19
59

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 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));
Hamed Baatour
  • 6,664
  • 3
  • 35
  • 47
35

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.
Lucius
  • 2,794
  • 4
  • 20
  • 42
Emmanuel Osimosu
  • 5,625
  • 2
  • 38
  • 39
22

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]]
drpicox
  • 229
  • 2
  • 2
16

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

Lucius
  • 2,794
  • 4
  • 20
  • 42
  • 3
    It is hard to understand this concept, can you please add comments to visual what you mean "returns an observable stream of observable stream of json". thanks. – user233232 Nov 02 '15 at 08:23
  • @user233232, like [x,x,x,x] to [[xxx],[[xxx],[xxx]]] – serkan Mar 03 '18 at 23:06
  • The key to understanding the first sentence is understanding that `flatMap` (and `map`) aren't special to arrays. It's possible to define these operations on any generic container or wrapper, including arrays, dictionaries, "optionals", reactive streams, promises, pointers, and even functions themselves. This is an emergent property of a mathematical construct called the monad. All the examples above meet the requirements for being a monad, and so they all can be given a definition of `map` and a `flatMap` (with some caveats). – mklbtz Sep 20 '18 at 17:40
14

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.

AkkarinZA
  • 591
  • 3
  • 9
14

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.

  • 1
    For anyone like me wondering why pipe is missing, pipe is used from rxjs 5.5 and above, but operators were combined with `.` in earlier versions as seen in this response. – piritocle Apr 07 '21 at 05:11
7

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)})
})
Thanawat
  • 71
  • 1
  • 1
5

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 to get it.

Map works like a for...each on each item in the array and transforms the items in the array but keeps the array as it is :

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

Flatmap does the same as map but also "flattens" the array :

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

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.

2

flatMap is used to flatten an array of arrays into a single array.

map simply converts one array to an other array. For example, suppose you have a list of person objects like this:

const friends = [
    {name: 'Dave', kids: ['Max', 'Jack']},
    {name: 'Max', kids: ['Sam', 'Alex', 'Megan']},
    {name: 'Jordan', kids: ['Mason', 'Cameron', 'Kaylin']}
];

But what you really need is an array of person names (i.e. strings: [“Dave”, “Max”, “Jordan”]). To convert this array of person object to array of strings, you would first define your mapping function like this:

const mapFunction = p -> p.name;

Then, use array.map like this:

const names = friends.map(mapFunction);

which returns:

["Dave", "Max", "Jordan"]

flatMap is similar to map in that you are converting one array into another array. But there are a few subtle differences: First of all, map is generally a one-to-one thing. The mapping function takes one object in and returns one object out:

p -> p.name

This means that 3 person objects in will produce 3 names out.

flatMap, on the other hand, is a one-to-many thing. The mapping function takes one object in but returns an array out:

p -> p.kids

The net result: 3 person objects in will produce 8 kid names out. Thus, this code:

const mapFunction = p -> p.kids;
const kidNames = friends.flatMap(mapFunction);

will return:

["Max", "Jack", "Sam", "Alex", "Megan", "Mason", "Cameron", "Kaylin"]
Tejas Savaliya
  • 572
  • 7
  • 8