Understanding mergeMap, concatMap, exhaust, switchMap functions

Just as for javascript arrays the function flatMap exists similarly to flatten higher level observables which are observables  inside observables which means inner observables. A good example of this is available in the official documentation 


const fileObservable = urlObservable.pipe(
   map(url => http.get(url)),
);

Now http.get also emits an Observable which could emit one or many strings  So now you have a Observables of Observables.

There are different ways you would like to deal with this inner Observable according to your application.
We will go through them one by one along with examples

1. mergeMap


const { fromEvent, interval } = rxjs;
const { scan, map, mapTo, mergeMap, take} = rxjs.operators;

const ones = fromEvent(document, 'click').pipe(mapTo(1));

// This observable gives a character everytime we click on the screen
const chars = ones.pipe(scan((acc, curr) => acc + curr, 64), 
map(x => String.fromCharCode(x)));

/*This observable emits 5 alphanumeric outputs combining
the outer and the inner observable at the interval of
every 1 sec */
const alphanumeric = chars.pipe(mergeMap(char => 
                     interval(1000).pipe(take(5), map(x => char+x))));

alphanumeric.subscribe(x => console.log(x));
  
Sample Output: A0 A1 A2 B0 A3 B1 A4 B2 C0 B3 C1 B4 C2 D0 C3 D1 C4 D2 D3 D4
Try out this code quickly in this plunker.
click on the screen 3 to 4 times in succession(may be not too rapidly)


We can observe that the inner observable of a stream of 5 numbers (actually alphanumeric because we combine the outer with the inner observable) starts every 1 second with the first click. The inner observable order is not important. As soon the next click is registered the inner observable of that click also starts emitting(it will start with B). Hence even if the earlier click emitting 5 numbers has not finished and if you click before that then the next inner observable will be emitted and the previous and current inner observables both will be actively emitting values simultaneously until they complete. Similar is the case for more number of clicks which means more inner observables and all of these inner observables  will be active at the same time.

2. concatMap


const { fromEvent, interval } = rxjs;
const { scan, map, mapTo, concatMap, take} = rxjs.operators;

const ones = fromEvent(document, 'click').pipe(mapTo(1));

// This observable gives a character everytime we click on the screen
const chars = ones.pipe(scan((acc, curr) => acc + curr, 64), 
map(x => String.fromCharCode(x)));

/*This observable emits 5 alphanumeric outputs combining
the outer and the inner observable at the interval of
every 1 sec */
const alphanumeric = chars.pipe(concatMap(char => 
                     interval(1000).pipe(take(5), map(x => char+x))));

alphanumeric.subscribe(x => console.log(x));

Sample output: A0 A1 A2 A3 A4 B0 B1 B2 B3 B4 C0 C1 C2 C3 C4 D0 D1 D2 D3 D4

Try out this code quickly in this plunker

click the screen a few times in a interval of a few seconds.

We can see that inner observable order is maintained. If the first inner observable (on the first click A ) starts and before completing (i.e from A0 ... A4) if we click on the screen a second time or any number of times, the first inner observable is completed and only then the second inner observable starts. Same is the case with a third click, unless the first and second observables have completed in order the third observable will not start. So concatMap preserves the order of the inner observables.

3. switchMap


const { fromEvent, interval } = rxjs;
const { scan, map, mapTo, switchMap, take} = rxjs.operators;

const ones = fromEvent(document, 'click').pipe(mapTo(1));

// This observable gives a character everytime we click on the screen
const chars = ones.pipe(scan((acc, curr) => acc + curr, 64), 
map(x => String.fromCharCode(x)));

/*This observable emits 5 alphanumeric outputs combining
the outer and the inner observable at the interval of
every 1 sec */
const alphanumeric = chars.pipe(switchMap(char => 
                     interval(1000).pipe(take(5), map(x => char+x))));

alphanumeric.subscribe(x => console.log(x));
Sample Output: A0 A1 B0 B1 B2 C0 C1 C2 D0 D1 D2 D3 D4
Try out this code quickly in this plunker

Click on the screen once and before the first set of 5 outputs completes click on the screen again

We observe here that as we click on the screen first time the first inner observable starts emitting the 5 alphanumeric values. But before this observable completes if we click on the screen a second time the second inner observable starts emitting and the remaining values from the first inner observables are discarded. So at any point in time when new inner observable values are emitted the earlier observable is stopped and the values from the current inner observable are emitted. On each new emission of the inner observable (in our case each new click) the previous inner observable is cancelled and the values from this new observables start emitting.

Comments