Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

map vs concatMap

I haven't been able to find the answer to this question, but what's the difference between concat map and map? Specifically, I have an example that made it very confusing for me:

const domainsObservable = this.auth.getAuthDomains()
    .shareReplay()
    .concatMap((authDomains) => authDomains.map((domain) => this.toDomain(domain, connectionsObservable)))
    .filter((authDomain) => this.isValidDomain(authDomain))
    .toArray();

This was calling a method from a service, getAuthDomains() that just returned an array of 3 domains. When I used the above implementation, I instead ended up with an array of 4 arrays of which each have an array of 3 domains. Somehow, it quadrupled the amount of data in the response.

I then switched over to the following implementation:

const domainsObservable = this.auth.getAuthDomains()
    .shareReplay()
    .map(authDomains => authDomains.map(domain => this.toDomain(domain, connectionsObservable)))
    .map(authDomains => authDomains.filter(domain => this.isValidDomain(domain)));  

This gave me exactly what I needed - a single array that was correctly filtered.

So why did switching over to map dramatically change the results? Did the order of my operations matter?

Thanks

like image 838
User 5842 Avatar asked Jan 27 '18 01:01

User 5842


1 Answers

Simply said the two operators do the following:

  • map() - projects each value into a different value that is propagated further down the chain:

    .map(v => v * 2)
    

    This is basically the same as Array.map().

  • concatMap() - projects each value into an Observable and subscribes to it. The operator is always subscribed to only one inner Observable so if the source Observable emits values faster they're buffered inside concatMap():

    .concatMap(v => Observable.of(v * 2))
    

    ...or typically in Angular you'd do something like this:

    .concatMap(v => this.http.get(`whatever/${v}`))
    

But here comes the interesting part. In RxJS 5 operators that subscribe to inner Observables (Observables returned from projection function, etc.) in fact expect so called "observable input". This means that you can freely interchange Observables, Promises, Observables-like objects, ... and also JavaScript arrays.

Internally it's implemented using the subscribeToResult function. Note that if you pass it an array it'll iterate it and emit each value separatelly: https://github.com/ReactiveX/rxjs/blob/master/src/internal/util/subscribeToResult.ts#L17

This means that you can use concatMap() to "unpack" arrays into single emissions. For example:

Observable.of(42)
  .concatMap(v => [1, 2, 3])
  .subscribe(console.log)

This will print numbers:

1
2
3

On the other hand if you used just map it would simply pass the array further:

Observable.of(42)
  .map(v => [1, 2, 3])
  .subscribe(console.log)

This will print numbers:

[1,2,3]

So concatMap is sometimes used only to unpack an array coming from the source Observable with concatMap(array => array) and that's exactly what's happening in your code.

Which one is better is up to you. Typically it's easier to perform simple operations like map or filter on the Array object itself instead of unpacking and processing in the Rx chain for performance reasons.

like image 65
martin Avatar answered Oct 27 '22 16:10

martin