乐闻世界logo
搜索文章和话题

What is the difference between switchMap, mergeMap, and concatMap in RxJS?

2月21日 16:55

Core Differences Between switchMap, mergeMap, and concatMap

These three operators are all used to handle higher-order Observables (Observable of Observable), but they have completely different processing strategies:

1. switchMap

Characteristics: Cancels previous inner Observable, only handles the latest one

How it works:

  • When a new value arrives, cancels the previous uncompleted Observable
  • Only keeps the result of the latest Observable
  • Suitable for scenarios where old requests need to be cancelled

Example code:

javascript
import { of, interval } from 'rxjs'; import { switchMap, take, delay } from 'rxjs/operators'; // Simulate async request function makeRequest(id) { return of(`Result ${id}`).pipe(delay(1000)); } interval(500).pipe( switchMap(id => makeRequest(id)), take(5) ).subscribe(console.log); // Output: Result 4 // Explanation: Because it triggers every 500ms, but requests take 1000ms // So each time a new request comes, the previous request is cancelled // Eventually only the last request completes

Practical use cases:

  • Search box input: Cancel previous search request on each input
  • Autocomplete: Only show results for latest input
  • Navigation switching: Cancel incomplete page loads
javascript
// Search box example searchInput.pipe( switchMap(query => searchAPI(query)) ).subscribe(results => { // Only display results from latest search displayResults(results); });

2. mergeMap

Characteristics: Processes all inner Observables in parallel

How it works:

  • Subscribes to all inner Observables simultaneously
  • Results from all Observables are emitted
  • No order guarantee, results may interleave

Example code:

javascript
import { of } from 'rxjs'; import { mergeMap, delay } from 'rxjs/operators'; function makeRequest(id) { return of(`Result ${id}`).pipe(delay(id * 200)); } of(1, 2, 3).pipe( mergeMap(id => makeRequest(id)) ).subscribe(console.log); // Output: Result 1, Result 2, Result 3 // Explanation: All requests execute in parallel, output in completion order

Practical use cases:

  • Loading multiple resources in parallel
  • Batch processing independent tasks
  • Concurrent requests that don't require ordering
javascript
// Parallel user data loading example merge( getUserProfile(userId), getUserPosts(userId), getUserComments(userId) ).pipe( mergeMap(response => response.json()) ).subscribe(data => { // All data loaded in parallel updateUI(data); });

3. concatMap

Characteristics: Processes inner Observables sequentially, one after another

How it works:

  • Subscribes to inner Observables in order
  • Subscribes to next only after current Observable completes
  • Guarantees result order

Example code:

javascript
import { of } from 'rxjs'; import { concatMap, delay } from 'rxjs/operators'; function makeRequest(id) { return of(`Result ${id}`).pipe(delay(500)); } of(1, 2, 3).pipe( concatMap(id => makeRequest(id)) ).subscribe(console.log); // Output: Result 1, Result 2, Result 3 // Explanation: Each request executes sequentially, next starts after previous completes

Practical use cases:

  • Requests that need to maintain order
  • Subsequent requests that depend on previous results
  • Preventing server overload
javascript
// Sequential file upload example files.pipe( concatMap(file => uploadFile(file)) ).subscribe(result => { // Files uploaded in order console.log('Uploaded:', result); });

Comparison Summary

FeatureswitchMapmergeMapconcatMap
ExecutionCancels old, keeps latestParallel executionSequential execution
Result orderOnly latest resultNo order guaranteeOrder guaranteed
Concurrency1 (only 1 at a time)Unlimited1 (only 1 at a time)
Use casesSearch, autocompleteParallel loadingSequential processing
PerformanceFastest (cancels old)Fastest (parallel)Slower (sequential)
Memory usageLowHighLow

Selection Guide

Use switchMap when:

  • Need to cancel old requests
  • Only care about latest results
  • Search, autocomplete scenarios
  • Avoid unnecessary network requests

Use mergeMap when:

  • Need parallel processing
  • No dependencies between requests
  • Need to maximize performance
  • Don't care about result order

Use concatMap when:

  • Need to maintain order
  • Dependencies between requests
  • Need to limit concurrency
  • Prevent server overload

Performance Considerations

javascript
// Performance comparison example const source = interval(100).pipe(take(10)); // switchMap: Only processes the last one source.pipe( switchMap(x => of(x).pipe(delay(1000))) ).subscribe(); // mergeMap: Processes all in parallel (may cause performance issues) source.pipe( mergeMap(x => of(x).pipe(delay(1000))) ).subscribe(); // concatMap: Processes sequentially (may be slower) source.pipe( concatMap(x => of(x).pipe(delay(1000))) ).subscribe();

Selection in Real Projects

javascript
// 1. Search functionality - Use switchMap searchInput.pipe( debounceTime(300), distinctUntilChanged(), switchMap(query => searchAPI(query)) ).subscribe(results => displayResults(results)); // 2. Batch loading - Use mergeMap productIds.pipe( mergeMap(id => getProductDetails(id)) ).subscribe(products => renderProducts(products)); // 3. Sequential operations - Use concatMap commands.pipe( concatMap(command => executeCommand(command)) ).subscribe(result => logResult(result));

Important Notes

  1. Memory leaks: mergeMap may create many concurrent requests, be mindful of memory usage
  2. Cancellation logic: switchMap automatically cancels, but concatMap and mergeMap don't
  3. Error handling: Any inner Observable error will cause the entire stream to fail
  4. Unsubscription: Always remember to unsubscribe to avoid memory leaks

Best Practices

javascript
// 1. Combine with error handling searchInput.pipe( switchMap(query => searchAPI(query).pipe( catchError(error => of([])) ) ) ).subscribe(results => displayResults(results)); // 2. Limit concurrency (using mergeMap) import { mergeMap } from 'rxjs/operators'; source.pipe( mergeMap(value => process(value), 3) // Limit concurrency to 3 ).subscribe(); // 3. Add retry mechanism source.pipe( switchMap(value => apiCall(value).pipe( retry(3), catchError(error => of(defaultValue)) ) ) ).subscribe();
标签:Rxjs