20个例子掌握RxJS——第三章使用 mergeMap 实现并发请求

13 阅读3分钟

RxJS 实战:使用 mergeMap 实现并发请求

概述

在实际开发中,我们经常需要并发执行多个请求。虽然 forkJoin 也可以实现并发,但它要求等待所有请求完成。而 mergeMap 提供了更灵活的并发控制,可以实时处理每个请求的结果。本章将详细介绍如何使用 mergeMap 实现并发请求。

mergeMap 操作符简介

mergeMap(也称为 flatMap)是 RxJS 中最常用的操作符之一。它会将源 Observable 的每个值映射为一个新的 Observable,然后合并这些 Observable 的输出。

基本语法

source$.pipe(
  mergeMap(value => createObservable(value))
)

关键特性

  1. 并发执行:默认情况下,mergeMap 会并发执行所有内部 Observable
  2. 实时输出:每个内部 Observable 完成后立即输出结果,不等待其他 Observable
  3. 顺序不保证:结果的顺序可能与输入顺序不同(按完成时间排序)
  4. 并发控制:可以通过第二个参数限制并发数

实战场景:并发请求多个接口

假设我们需要并发请求 10 个接口(delay1、delay2、delay3 循环),并实时显示每个请求的结果。

实现思路

  1. 使用 from 将接口 URL 数组转换为 Observable
  2. 使用 mergeMap 并发执行每个请求
  3. 使用 catchError 处理单个请求的错误
  4. 使用 toArray 收集所有结果(可选,如果需要等待全部完成)

核心代码

const apiUrls = [
  '/api/delay1',
  '/api/delay2',
  '/api/delay3',
  '/api/delay1',
  '/api/delay2',
  '/api/delay3',
  '/api/delay1',
  '/api/delay2',
  '/api/delay3',
  '/api/delay1'
];

from(apiUrls)
  .pipe(
    mergeMap((url, index) => {
      const apiName = url.replace('/api/', '');
      return this.http.get<DelayApiResponse>(`${this.apiBaseUrl}${url}`)
        .pipe(
          catchError(err => {
            // 捕获单个请求错误,返回错误响应
            console.error(`请求 ${url} 失败:`, err);
            return of({
              success: false,
              message: err.message || '请求失败',
              data: {
                delay: null as any,
                timestamp: new Date().toISOString(),
                info: '请求失败'
              }
            } as DelayApiResponse);
          }),
          map(response => {
            // 将响应包装为包含接口名称的对象
            const result: RequestResult = {
              apiName,
              apiUrl: url,
              response,
              index
            };
            return result;
          })
        );
    }),
    toArray() // 收集所有结果
  )
  .subscribe({
    next: (results: RequestResult[]) => {
      // 所有请求完成(按完成顺序,不是原始顺序)
      this.results = results;
      console.log('所有请求完成:', results);
    }
  });

关键点解析

1. 并发执行机制

mergeMap 默认并发数为 Infinity,意味着理论上所有请求都会并发执行。但实际并发数可能受以下因素限制:

  • 浏览器并发连接限制:同一域名通常最多 6 个并发连接
  • HTTP 客户端限制:某些 HTTP 客户端可能有自己的并发限制
  • 服务器限制:服务器可能限制同一客户端的并发连接数

2. 结果顺序

mergeMap 输出的结果顺序是按照完成时间排序的,而不是输入顺序。如果需要保持原始顺序,可以使用 concatMaptoArray() 后再排序。

3. 错误处理

使用 catchError 可以捕获单个请求的错误,避免整个流中断。这样即使某个请求失败,其他请求仍能继续执行。

4. 并发数控制

如果需要限制并发数,可以在 mergeMap 的第二个参数中指定:

mergeMap((url, index) => {
  return this.http.get(url);
}, 3) // 最多同时 3 个请求

与 forkJoin 的对比

特性mergeMapforkJoin
执行方式并发,实时输出并发,等待全部完成
结果顺序按完成时间按输入顺序
错误处理可单独处理任一失败整体失败
适用场景需要实时处理结果需要等待全部完成

实际应用场景

1. 批量数据验证

// 并发验证多个用户ID是否存在
from(userIds)
  .pipe(
    mergeMap(userId => 
      this.http.get(`/api/users/${userId}`).pipe(
        catchError(() => of(null))
      )
    )
  )
  .subscribe(result => {
    // 实时处理每个验证结果
    if (result) {
      console.log('用户存在:', result);
    }
  });

2. 文件上传进度

// 并发上传多个文件,实时显示进度
from(files)
  .pipe(
    mergeMap(file => 
      this.uploadFile(file).pipe(
        map(progress => ({ file, progress }))
      )
    )
  )
  .subscribe(({ file, progress }) => {
    // 实时更新每个文件的上传进度
    updateFileProgress(file, progress);
  });

3. 数据采集

// 从多个数据源并发采集数据
from(dataSources)
  .pipe(
    mergeMap(source => 
      this.fetchData(source).pipe(
        catchError(err => {
          console.error(`数据源 ${source} 失败:`, err);
          return EMPTY;
        })
      )
    )
  )
  .subscribe(data => {
    // 实时处理采集到的数据
    processData(data);
  });

性能优化建议

1. 限制并发数

如果请求数量很大,建议限制并发数,避免服务器压力过大:

mergeMap(url => this.http.get(url), 5) // 最多 5 个并发

2. 使用 exhaustMap 避免重复请求

如果同一个请求可能被触发多次,可以使用 exhaustMap 忽略后续请求:

exhaustMap(url => this.http.get(url))

3. 添加重试机制

对于可能失败的请求,可以添加重试:

mergeMap(url => 
  this.http.get(url).pipe(
    retry(3), // 失败后重试 3 次
    catchError(err => of(null))
  )
)

注意事项

  1. 内存占用:如果请求数量非常大,需要注意内存占用
  2. 服务器压力:并发请求会给服务器带来压力,需要合理控制并发数
  3. 错误处理:确保每个请求都有适当的错误处理,避免整个流中断
  4. 结果顺序:如果需要保持顺序,考虑使用 concatMaptoArray() 后排序

总结

mergeMap 是实现并发请求的强大工具,它提供了灵活的并发控制和实时结果处理能力。在实际项目中,根据具体需求选择合适的策略:

  • 需要实时处理结果 → 使用 mergeMap
  • 需要等待全部完成 → 使用 forkJoin
  • 需要保证顺序 → 使用 concatMap
  • 需要限制并发 → 使用 mergeMap 配合并发数参数

通过合理使用 mergeMap,我们可以构建高效、响应迅速的异步数据流处理系统。

码云地址:gitee.com/leeyamaster…