AngularRxJS

RxJS exhaustMap Operator

RxJS exhaustMap operator, visually demonstrating how it processes only one emission at a time while ignoring subsequent emissions until the active inner Observable completes

The exhaustMap operator in RxJS is a powerful tool for managing scenarios where only one Observable should be active at a time. It is commonly used when handling user actions like button clicks, where you want to ignore subsequent emissions while the current task is still processing.

In this article, we’ll dive into what exhaustMap is, how it works, its syntax, and practical examples to showcase its usage.


What is exhaustMap?

The exhaustMap operator:

  • Maps each value from a source Observable to an inner Observable.
  • Subscribes to the inner Observable only if there is no currently active subscription.
  • Ignores any new emissions from the source Observable while the inner Observable is active.

Key Features

  1. Single Active Subscription: Ensures only one inner Observable is active at any given time.
  2. Drop Subsequent Emissions: Ignores new source emissions until the active inner Observable completes.
  3. Ideal for Debouncing: Particularly useful in user interaction scenarios to avoid processing overlapping actions.

Syntax

typescriptCopy codeexhaustMap(project: (value: T, index: number) => ObservableInput, resultSelector?: (outerValue, innerValue, outerIndex, innerIndex) => any): OperatorFunction

Parameters

  1. project: A function that maps the source Observable’s value to an inner Observable.
    • Takes the emitted value and an optional index as arguments.
  2. resultSelector (optional): A function to combine the outer and inner Observable values.

Returns

  • An Observable that emits values from the inner Observable if no other inner Observable is active.

Importing exhaustMap

To use exhaustMap, import it from rxjs/operators:

typescriptCopy codeimport { exhaustMap } from 'rxjs/operators';

How exhaustMap Works

  1. The source Observable emits a value.
  2. If there is no active inner Observable, exhaustMap maps the emitted value to an inner Observable and subscribes to it.
  3. While the inner Observable is active, any new emissions from the source Observable are ignored.
  4. Once the inner Observable completes, the next emission from the source Observable is processed.

Examples

Example 1: Ignoring Rapid Clicks

Simulating a button click that triggers an API call, ignoring subsequent clicks until the previous request completes.

typescriptCopy codeimport { fromEvent, of } from 'rxjs';
import { exhaustMap, delay } from 'rxjs/operators';
const button = document.getElementById('action-button');
const clicks$ = fromEvent(button!, 'click');
clicks$
  .pipe(
    exhaustMap(() => {
      console.log('API call started');
      return of('API Response').pipe(delay(3000)); // Simulate 3-second API call
    })
  )
  .subscribe((response) => console.log(response));
// Output:
// API call started
// API Response (after 3 seconds)
// (Subsequent clicks during the 3 seconds are ignored)

Explanation

  • The first click triggers an API call, represented by a 3-second delayed Observable.
  • Any clicks during the active request are ignored until the current Observable completes.

Example 2: Form Submission

Simulating a form submission where only one request is processed at a time.

typescriptCopy codeimport { fromEvent, of } from 'rxjs';
import { exhaustMap, delay } from 'rxjs/operators';
const form = document.getElementById('submit-form');
const formSubmit$ = fromEvent(form!, 'submit');
formSubmit$
  .pipe(
    exhaustMap(() => {
      console.log('Form submitted');
      return of('Form processed').pipe(delay(2000)); // Simulate processing time
    })
  )
  .subscribe((result) => console.log(result));
// Output:
// Form submitted
// Form processed (after 2 seconds)
// (Subsequent submissions during processing are ignored)

Explanation

  • The first form submission is processed.
  • Any submissions while processing is active are ignored.

Example 3: Managing HTTP Requests

Simulating a scenario where multiple HTTP requests could overlap but only the first is processed at a time.

typescriptCopy codeimport { of } from 'rxjs';
import { exhaustMap, delay } from 'rxjs/operators';
const apiRequest$ = of('API Request').pipe(delay(3000)); // Simulate API response
const source$ = of(1, 2, 3, 4);
source$
  .pipe(
    exhaustMap(() => apiRequest$)
  )
  .subscribe((response) => console.log(response));
// Output:
// API Request (after 3 seconds)
// (Subsequent emissions are ignored during the active request)

Explanation

  • The first emission triggers an HTTP request.
  • Any emissions while the request is processing are ignored.

Comparison with Other Operators

exhaustMap vs mergeMap

  • mergeMap: Subscribes to all inner Observables concurrently.
  • exhaustMap: Subscribes to only one inner Observable at a time, ignoring others.

exhaustMap vs switchMap

  • switchMap: Cancels the active inner Observable when a new value is emitted.
  • exhaustMap: Ignores new emissions while the active inner Observable is running.

exhaustMap vs concatMap

  • concatMap: Queues new emissions and processes them sequentially.
  • exhaustMap: Drops new emissions while processing the current one.

Use Cases

  1. Debouncing User Actions: Prevent duplicate form submissions or rapid button clicks.
  2. Long-Running Tasks: Ensure only one long-running task (e.g., file upload) is processed at a time.
  3. Rate-Limiting API Calls: Avoid overlapping HTTP requests that could strain the server.

Best Practices

  1. Avoid Overuse: Use exhaustMap only when it makes sense to drop intermediate emissions.
  2. Error Handling: Combine with catchError to handle errors in the inner Observable gracefully.
  3. Use for Non-Critical Emissions: Prefer exhaustMap for scenarios where missing emissions is acceptable.

Conclusion

The exhaustMap operator is a practical choice for scenarios where you want to ensure that only one task or process runs at a time. By ignoring subsequent emissions while an inner Observable is active, exhaustMap helps manage user interactions and asynchronous workflows efficiently.

Understanding when and how to use exhaustMap can simplify your code and make your RxJS-based applications more robust. Experiment with it in your projects and see how it helps streamline complex workflows.

Leave a Reply

Your email address will not be published. Required fields are marked *