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

RxJS 中 switchMap、mergeMap、concatMap 有什么区别?

2026年2月21日 16:55

switchMap、mergeMap、concatMap 的核心区别

这三个操作符都是用于处理高阶 Observable(Observable of Observable)的,但它们的处理策略完全不同:

1. switchMap

特点: 取消之前的内部 Observable,只处理最新的

工作原理:

  • 当新的值到达时,取消之前未完成的 Observable
  • 只保留最新的 Observable 的结果
  • 适合需要取消旧请求的场景

示例代码:

javascript
import { of, interval } from 'rxjs'; import { switchMap, take, delay } from 'rxjs/operators'; // 模拟异步请求 function makeRequest(id) { return of(`Result ${id}`).pipe(delay(1000)); } interval(500).pipe( switchMap(id => makeRequest(id)), take(5) ).subscribe(console.log); // 输出: Result 4 // 解释: 因为每500ms触发一次,但请求需要1000ms // 所以每次新请求来时,之前的请求都被取消了 // 最终只有最后一个请求完成了

实际应用场景:

  • 搜索框输入:每次输入都取消上一次的搜索请求
  • 自动完成:只显示最新输入的结果
  • 导航切换:取消未完成的页面加载
javascript
// 搜索框示例 searchInput.pipe( switchMap(query => searchAPI(query)) ).subscribe(results => { // 只显示最新搜索的结果 displayResults(results); });

2. mergeMap

特点: 并行处理所有内部 Observable

工作原理:

  • 同时订阅所有内部 Observable
  • 所有 Observable 的结果都会被发出
  • 不保证顺序,结果可能交错

示例代码:

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); // 输出: Result 1, Result 2, Result 3 // 解释: 所有请求并行执行,按完成顺序输出

实际应用场景:

  • 并行加载多个资源
  • 批量处理独立任务
  • 不需要顺序的并发请求
javascript
// 并行加载用户数据示例 merge( getUserProfile(userId), getUserPosts(userId), getUserComments(userId) ).pipe( mergeMap(response => response.json()) ).subscribe(data => { // 所有数据并行加载完成 updateUI(data); });

3. concatMap

特点: 顺序处理内部 Observable,一个完成后再处理下一个

工作原理:

  • 按顺序订阅内部 Observable
  • 当前 Observable 完成后才订阅下一个
  • 保证结果的顺序

示例代码:

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); // 输出: Result 1, Result 2, Result 3 // 解释: 每个请求按顺序执行,前一个完成后才执行下一个

实际应用场景:

  • 需要保证顺序的请求
  • 依赖前一个结果的后续请求
  • 防止服务器过载
javascript
// 顺序上传文件示例 files.pipe( concatMap(file => uploadFile(file)) ).subscribe(result => { // 文件按顺序上传 console.log('Uploaded:', result); });

对比总结

特性switchMapmergeMapconcatMap
执行方式取消旧的,只保留最新的并行执行所有顺序执行
结果顺序只保留最新结果不保证顺序保证顺序
并发数1(同时只有1个)无限制1(同时只有1个)
适用场景搜索、自动完成并行加载顺序处理
性能最快(取消旧请求)最快(并行)较慢(顺序)
内存占用

选择指南

使用 switchMap 当:

  • 需要取消旧请求
  • 只关心最新结果
  • 搜索、自动完成等场景
  • 避免不必要的网络请求

使用 mergeMap 当:

  • 需要并行处理
  • 请求之间没有依赖
  • 需要最大化性能
  • 不关心结果顺序

使用 concatMap 当:

  • 需要保证顺序
  • 请求之间有依赖
  • 需要限制并发数
  • 避免服务器过载

性能考虑

javascript
// 性能对比示例 const source = interval(100).pipe(take(10)); // switchMap: 只处理最后一个 source.pipe( switchMap(x => of(x).pipe(delay(1000))) ).subscribe(); // mergeMap: 并行处理所有(可能造成性能问题) source.pipe( mergeMap(x => of(x).pipe(delay(1000))) ).subscribe(); // concatMap: 顺序处理(可能较慢) source.pipe( concatMap(x => of(x).pipe(delay(1000))) ).subscribe();

实际项目中的选择

javascript
// 1. 搜索功能 - 使用 switchMap searchInput.pipe( debounceTime(300), distinctUntilChanged(), switchMap(query => searchAPI(query)) ).subscribe(results => displayResults(results)); // 2. 批量加载 - 使用 mergeMap productIds.pipe( mergeMap(id => getProductDetails(id)) ).subscribe(products => renderProducts(products)); // 3. 顺序操作 - 使用 concatMap commands.pipe( concatMap(command => executeCommand(command)) ).subscribe(result => logResult(result));

注意事项

  1. 内存泄漏: mergeMap 可能创建大量并发请求,需要注意内存使用
  2. 取消逻辑: switchMap 会自动取消,但 concatMap 和 mergeMap 不会
  3. 错误处理: 任何一个内部 Observable 出错都会导致整个流失败
  4. 取消订阅: 始终记得取消订阅以避免内存泄漏

最佳实践

javascript
// 1. 结合错误处理 searchInput.pipe( switchMap(query => searchAPI(query).pipe( catchError(error => of([])) ) ) ).subscribe(results => displayResults(results)); // 2. 限制并发数(使用 mergeMap) import { mergeMap } from 'rxjs/operators'; source.pipe( mergeMap(value => process(value), 3) // 限制并发数为3 ).subscribe(); // 3. 添加重试机制 source.pipe( switchMap(value => apiCall(value).pipe( retry(3), catchError(error => of(defaultValue)) ) ) ).subscribe();
标签:Rxjs