The RxJS exhaustMap operator is another one of those flattening operators. It maps to an inner Observable but ignores values from the source Observable until this inner Observable completes.
RxJS exhaustMap ignores other values until completion
Other variants of similar mapping or flattening operators (concatMap, switchMap, mergeMap) would always consider the new values in some way. The exhaustMap operator is different. Let’s take a look at the following example and break it down to see what exactly happens:
interval(1).pipe(
take(8),
exhaustMap((v: number) => of(v).pipe(
delay(5)
)
)).subscribe(console.log);
// logs:
// 0
// 4
Here is what is going on:
- The interval(1) emits a number every millisecond starting with 0 and continuing with 1,2,3,…
- A pipe is added to that interval where we use take(8). Therefore only the values 0,1,2,3,4,5,6,7 will be emitted.
- Now exhaustMap comes into play
- generally we map the received number from interval to an inner Observable using of()
- We delay the inner Observable’s emission and therefore also its completion by 5 ms. During this delay the values from the interval are ignore thanks to exhaustMap.
- Finally we subscribe to the stream and log the output which is 0,4
- Because the values are ignore during the delay we won’t see 1,2 and 3 in the output
- When the delay is over and the inner Observable with of() and delay() has completed a new value is accepted, which is 4
- Further values emitted by the interval are again ignored. Hence 5,6 and 7 are not part of the final stream’s values.
Do not trigger another http call for another source value
If the inner Observable’s work is resource consuming and you don’t want to stop it or execute it again while it’s running, then exhaustMap comes to the rescue. Http calls are usually something you only want to execute when really needed. Therefore exhaustMap can make sense in the following example:
@Effect()
createObject$ = this.actions$.pipe(
ofType(ObjectActionTypes.Create),
map((action: CreateObject) => action.payload),
exhaustMap((object: ObjectData) =>
this.objectHttpService
.create(object)
.pipe(
map(object => new ObjectCreated({ object }))
)
)
);
Let’s imagine your application follows the Redux pattern and is powered by NgRx (state management). In above code we have an Action for creating objects. This action can be triggered in various ways, e.g. button click, keyboard short cut, etc. Once an action is triggered you want the object creation to finish before continuing with a next object. Here the exhaustMap only considers new actions when the objectHttpService has completed its work. Like that you are not overloading the service and creating one object after another.
Exercise for the exhaustMap operator 💪
Create a list of ignored values based on the following code:
interval(1).pipe(
take(8),
exhaustMap((v: number) => of(v).pipe(
delay(5)
)
)).subscribe(console.log);
// logs:
// 0
// 4
As always the code examples and exercise solution can be found on GitHub.