RxJS 高阶映射操作符详解:map、mergeMap 和 switchMap

1. map 操作符

map 是最基本的转换操作符,用于对 Observable 发出的每个值进行一对一转换。

基本特点:

  • 同步操作
  • 一对一转换
  • 不改变 Observable 的发出时机

详细示例:

import { of } from 'rxjs';
import { map } from 'rxjs/operators';// 示例1:简单数值转换
of(1, 2, 3).pipe(map(x => x * 2)
).subscribe(result => console.log(result));
// 输出: 2, 4, 6// 示例2:对象属性转换
const users = of({ id: 1, name: 'Alice', age: 25 },{ id: 2, name: 'Bob', age: 30 }
);users.pipe(map(user => ({...user,name: user.name.toUpperCase(),isAdult: user.age >= 18}))
).subscribe(console.log);
/* 输出:
{id: 1, name: "ALICE", age: 25, isAdult: true}
{id: 2, name: "BOB", age: 30, isAdult: true}
*/// 示例3:API响应处理
import { from } from 'rxjs';function fetchUser(userId) {return from(fetch(`https://jsonplaceholder.typicode.com/users/${userId}`).then(response => response.json()));
}fetchUser(1).pipe(map(user => user.name)
).subscribe(name => console.log('用户名:', name));
// 输出: 用户名: Leanne Graham

2. mergeMap (flatMap) 操作符

mergeMap 用于将一个值映射成一个新的 Observable,并同时订阅所有这些内部 Observable,合并它们的输出。

基本特点:

  • 一对多转换
  • 同时维护多个内部订阅
  • 不取消之前的订阅
  • 适合并行处理

详细示例:

import { of, interval } from 'rxjs';
import { mergeMap, take } from 'rxjs/operators';// 示例1:将每个值转换为新的Observable
of('a', 'b', 'c').pipe(mergeMap(letter => interval(1000).pipe(take(3),map(i => letter + i)))
).subscribe(console.log);
/* 输出 (每秒一个):
a0
b0
c0
a1
b1
c1
a2
b2
c2
*/// 示例2:实际API请求
function fetchPosts(userId) {return from(fetch(`https://jsonplaceholder.typicode.com/posts?userId=${userId}`).then(response => response.json()));
}of(1, 2, 3).pipe(mergeMap(userId => fetchPosts(userId))
).subscribe(posts => {console.log(`用户${posts[0]?.userId}的帖子数:`, posts.length);
});
/* 输出:
用户1的帖子数: 10
用户2的帖子数: 10
用户3的帖子数: 10
*/// 示例3:结合Promise使用
function uploadFile(file) {return new Promise(resolve => {setTimeout(() => {resolve(`文件${file.name}上传成功`);}, 1000);});
}const files = of({ name: 'document.pdf' },{ name: 'image.jpg' }
);files.pipe(mergeMap(file => from(uploadFile(file)))
).subscribe(result => console.log(result));
/* 输出 (大约同时):
文件document.pdf上传成功
文件image.jpg上传成功
*/

3. switchMap 操作符

switchMap 会在每次发出新值时取消之前的内部 Observable 订阅,并订阅新的 Observable。

基本特点:

  • 一对多转换
  • 只保留最新的内部订阅
  • 自动取消之前的订阅
  • 适合搜索输入等场景

详细示例:

import { fromEvent, interval } from 'rxjs';
import { switchMap, take } from 'rxjs/operators';// 示例1:基础用法
fromEvent(document.getElementById('searchInput'), 'input').pipe(switchMap(event => {const query = event.target.value;return from(fetch(`https://api.example.com/search?q=${query}`).then(res => res.json()));})
).subscribe(results => {console.log('搜索结果:', results);
});// 示例2:与interval结合
const source$ = of('start').pipe(switchMap(() => interval(1000).pipe(take(5),map(i => `${i}`)))
);source$.subscribe(console.log);
/* 输出 (每秒一个):
值 0
值 1
值 2
值 3
值 4
*/// 示例3:实际应用 - 取消之前的请求
function searchBooks(query) {return from(fetch(`https://openlibrary.org/search.json?q=${query}`).then(res => res.json()));
}const searchInput = document.getElementById('bookSearch');
fromEvent(searchInput, 'input').pipe(map(event => event.target.value),filter(query => query.length > 2), // 至少3个字符才搜索debounceTime(300), // 防抖300msdistinctUntilChanged(), // 值变化才继续switchMap(query => searchBooks(query))
).subscribe(results => {console.log('找到书籍:', results.docs);
});

4. tap 操作符

tap 用于在 Observable 流中执行副作用操作,不影响数据流。

基本特点:

  • 用于调试、日志记录
  • 执行副作用但不修改数据
  • 不影响原始流

详细示例:

import { of } from 'rxjs';
import { tap, map } from 'rxjs/operators';// 示例1:基本调试
of(1, 2, 3).pipe(tap(value => console.log('收到原始值:', value)),map(x => x * 2),tap(value => console.log('转换后的值:', value))
).subscribe();
/* 输出:
收到原始值: 1
转换后的值: 2
收到原始值: 2
转换后的值: 4
收到原始值: 3
转换后的值: 6
*/// 示例2:实际应用 - 记录API请求
function getUser(userId) {return from(fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)).pipe(tap(response => {console.log(`请求用户${userId}的状态:`, response.status);if (!response.ok) {throw new Error('请求失败');}}),switchMap(response => from(response.json())));
}getUser(1).pipe(tap(user => {console.log('成功获取用户:', user.name);// 可以在这里更新UI状态document.getElementById('loading').style.display = 'none';}),catchError(error => {console.error('获取用户失败:', error);return of(null);})
).subscribe();// 示例3:状态管理
let requestCount = 0;function fetchData() {return from(fetch('/api/data')).pipe(tap(() => {requestCount++;console.log(`当前请求数: ${requestCount}`);document.getElementById('spinner').style.display = 'block';}),switchMap(response => response.json()),tap(() => {document.getElementById('spinner').style.display = 'none';}));
}

综合对比示例

import { of, fromEvent } from 'rxjs';
import { map, mergeMap, switchMap, tap, delay } from 'rxjs/operators';// 模拟API函数
function simulateApi(id, delayTime) {return of(`结果 ${id}`).pipe(delay(delayTime));
}// 对比mergeMap和switchMap
const button = document.getElementById('myButton');fromEvent(button, 'click').pipe(map((_, index) => index + 1), // 点击次数tap(count => console.log(`点击 #${count}`)),// 尝试切换这两个操作符查看区别// mergeMap(count => simulateApi(count, 1000))switchMap(count => simulateApi(count, 1000))
).subscribe(result => console.log('收到:', result));/* 
使用mergeMap时的可能输出:
点击 #1
点击 #2
点击 #3
收到: 结果 1
收到: 结果 2
收到: 结果 3使用switchMap时的可能输出:
点击 #1
点击 #2 (在1秒内点击)
点击 #3 (在1秒内点击)
收到: 结果 3 (只收到最后一个结果)
*/

实际应用场景总结

  1. map

    • 简单数据转换
    • 提取对象属性
    • 格式化数据
  2. mergeMap

    • 并行处理多个请求
    • 文件上传
    • 需要保留所有内部订阅的场景
  3. switchMap

    • 搜索输入自动完成
    • 取消之前的HTTP请求
    • 路由导航
  4. tap

    • 调试和日志记录
    • 执行UI更新等副作用
    • 监控状态变化

记住选择操作符的关键:

  • 是否需要并行处理?→ mergeMap
  • 是否需要取消之前的请求?→ switchMap
  • 是否需要简单转换?→ map
  • 是否需要执行副作用?→ tap

在 RxJS 的 pipe 中,操作符的执行顺序是从上到下、从左到右的线性执行流程。让我们通过你提到的 maptapswitchMap 组合来分析它们的执行顺序。

基本执行顺序规则

  1. 数据流经 pipe 的顺序:从第一个操作符开始,依次传递给后续操作符
  2. 同步操作符(如 maptap):立即执行
  3. 异步/高阶操作符(如 switchMap):会创建新的 Observable
  4. 每个操作符都会接收上一个操作符的输出

典型组合示例分析

import { fromEvent } from 'rxjs';
import { map, tap, switchMap } from 'rxjs/operators';fromEvent(document.getElementById('searchInput'), 'input').pipe(tap(event => console.log('1. 原始事件:', event)),        // 第1步map(event => event.target.value),                      // 第2步tap(value => console.log('2. 提取的值:', value)),      // 第3步switchMap(query => fetch(`/api/search?q=${query}`))    // 第4步
).subscribe(response => {console.log('5. 最终结果:', response);                 // 第5步
});

详细执行流程

假设用户在搜索输入框中输入 “rxjs”:

  1. tap(event => console.log('1. 原始事件:', event))

    • 最先执行
    • 打印原始的 input 事件对象
    • 不影响数据流,将原样传递事件对象
  2. map(event => event.target.value)

    • 从事件对象中提取输入框的值
    • event 转换为字符串值(如 “rxjs”)
  3. tap(value => console.log('2. 提取的值:', value))

    • 打印提取后的值
    • 不影响数据流,原样传递值
  4. switchMap(query => fetch(/api/search?q=${query}))

    • 高阶操作,会做以下事情:
      a. 取消之前可能未完成的 fetch 请求(如果是连续输入)
      b. 发起新的 fetch 请求
      c. 将 Promise 转换为 Observable
    • 等待 API 响应
  5. subscribe 回调

    • 最后执行
    • 收到 fetch 的响应对象

可视化执行顺序

输入事件 → tap(记录原始事件) → map(提取值) → tap(记录值) → switchMap(发起请求) → 订阅接收响应

关键注意事项

  1. switchMap 的特殊行为

    • 当新值到达时,会取消前一个内部 Observable
    • 这意味着如果用户快速连续输入,只有最后一次请求会被处理
  2. tap 的位置影响

    // 示例1:tap在switchMap之前
    .pipe(tap(x => console.log('before switchMap', x)),switchMap(x => fetchData(x))
    )// 示例2:tap在switchMap之后
    .pipe(switchMap(x => fetchData(x)),tap(x => console.log('after switchMap', x))
    )
    
    • 示例1的 tap 记录的是 switchMap 前的值
    • 示例2的 tap 记录的是 API 返回的数据
  3. 错误处理

    • 如果 switchMap 内部的 Observable 出错,错误会跳过后续操作符直接到达 subscribe 的 error 回调

实际场景执行示例

假设我们有一个搜索框,用户依次输入:r → rx → rxj → rxjs

fromEvent(searchInput, 'input').pipe(debounceTime(300),map(event => event.target.value),tap(query => console.log(`正在处理查询: ${query}`)),switchMap(query => from(fetch(`/api/search?q=${query}`).then(res => res.json()))
).subscribe({next: results => console.log('搜索结果:', results),error: err => console.error('搜索失败:', err)
});

可能的执行过程:

  1. 用户输入 ‘r’ → 300ms 没有新输入 → 触发搜索

    • tap: “正在处理查询: r”
    • switchMap: 发起搜索 “r” 的请求
  2. 用户在 300ms 内继续输入 ‘rx’ → 取消 ‘r’ 的请求

    • tap: “正在处理查询: rx”
    • switchMap: 发起搜索 “rx” 的请求
  3. 用户继续快速输入 ‘rxj’ → 取消 ‘rx’ 的请求

    • tap: “正在处理查询: rxj”
    • switchMap: 发起搜索 “rxj” 的请求
  4. 用户最后输入 ‘rxjs’ 并停止 300ms → 取消 ‘rxj’ 的请求

    • tap: “正在处理查询: rxjs”
    • switchMap: 发起搜索 “rxjs” 的请求
    • 最终只有 “rxjs” 的搜索结果会到达 subscribe

总结执行顺序要点

  1. 线性流水线处理:数据像水流一样依次通过每个操作符
  2. 同步操作符立即执行:map 和 tap 会立即对每个值进行处理
  3. switchMap 是异步边界:它会开启一个新的异步上下文
  4. 订阅只看到最终结果:subscribe 收到的是经过整个管道处理后的结果
  5. 错误会跳过后续步骤:任何操作符出错都会直接跳到错误处理

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/web/80969.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于stm32的多旋翼无人机(Multi-rotor UAV based on stm32)

由于一直在调试本项目,好久没有发文章,最近本项目的PID调试初见成效!开始正文前首先感谢各位粉丝的支持,以及对本项目技术上支持的老师以及师兄,谢谢你们! 对应源码及文件:源码及文件下载 基于…

量子传感器:开启微观世界的精准探测

随着量子技术的飞速发展,量子传感器逐渐成为前沿科技领域的热门研究方向。量子传感器利用量子力学的特性,能够实现对物理量的极高精度测量,其应用范围涵盖了基础科学研究、医学诊断、环境监测以及国防安全等多个领域。本文将深入探讨量子传感…

河道管网排口在线监测系统解决方案

一、方案概述 我国作为世界上河流数量最为丰富的国家之一,拥有众多历史悠久的壮阔江河流域。然而,伴随经济社会的迅猛发展,河湖管理与保护面临诸多新挑战,诸如河道干涸、湖泊萎缩、水环境恶化以及河湖功能退化等问题,对…

Foldseek快速蛋白质结构比对

1. 下载和安装 Foldseek 如果只是单个蛋白质结构的序列比对,我们只需要用Foldseek 的网站服务 https://search.foldseek.com/search 上传我们的蛋白质结构并选择想要进行比对的数据库即可,这里不做重点讲解。做生物信息学研究,我们难免需要批…

宏山激光韩国釜山开放日圆满举行,服务本地化再提速

5月21日-22日,宏山激光在韩国釜山展厅举办了主题为“韩国本地服务领导者”的开放日活动,此次活动聚焦韩国市场,通过沉浸式参观和深度交流,全面展示宏山激光本地化服务体系的建设成果,彰显其服务本地、深耕市场的坚定决…

大模型「瘦身」指南:从LLaMA到MobileBERT的轻量化部署实战

大模型「瘦身」指南:从LLaMA到MobileBERT的轻量化部署实战 系统化学习人工智能网站(收藏):https://www.captainbed.cn/flu 文章目录 大模型「瘦身」指南:从LLaMA到MobileBERT的轻量化部署实战摘要引言一、轻量化技术…

JavaScript篇:函数作用域与作用域链探秘

大家好,我是江城开朗的豌豆,一名拥有6年以上前端开发经验的工程师。我精通HTML、CSS、JavaScript等基础前端技术,并深入掌握Vue、React、Uniapp、Flutter等主流框架,能够高效解决各类前端开发问题。在我的技术栈中,除了…

Robust Kernel Estimation with Outliers Handling for Image Deblurring论文阅读

Robust Kernel Estimation with Outliers Handling for Image Deblurring 1. 论文的研究目标与实际问题意义1.1 研究目标1.2 实际问题与产业意义2. 论文的创新方法、模型与优势2.1 核心思路2.2 关键公式与技术细节2.2.1 非线性模糊模型与能量函数2.2.2 中间潜像更新与IRLS2.2.3…

nginx配置跨域请求,后台不用配置啦,完美

允许全部把域名改* server { listen 22222; server_name localhost; location / { if ($request_method OPTIONS) { add_header Access-Control-Allow-Origin http://localhost:8080; add_header Access-Control-Allow-Headers *; add_header Access-Control-…

[特殊字符] 构建高内聚低耦合的接口架构:从数据校验到后置通知的分层实践

在现代企业系统开发中,接口结构设计的质量直接影响系统的稳定性、扩展性与可维护性。随着业务复杂度上升,单一层次的接口实现往往难以应对功能膨胀、事务一致性、后置扩展等需求。因此,我们提出一种面向复杂业务场景的接口分层模型&#xff0…

MySQL 5.7 实战:JSON 字段提取、Base64 解码与引号问题全解析

一、背景与问题场景 在 MySQL 数据库中,存储 JSON 格式数据(如用户行为日志、配置参数、扩展信息)的场景日益普遍。当需要从 JSON 字段中提取特定键值(如info)并进行 Base64 解码时,常遇到以下问题&#x…

1.2.1+1.2.2计算机硬件的基本组成

知识总览 早期冯诺依曼计算机:从人工-》自动 出现原因: 埃尼阿克计算机每执行一条指令都需要人工接线揽,虽然计算机处理的快,但是人工接线可能慢,效率低,于是出现冯诺依曼计算机,把要执行的指…

Spring AI 1.0 GA 正式发布

Spring AI 1.0 GA 正式发布 快速入门核心特性1. **增强型 LLM(大语言模型)**2. **MCP 协议支持**3. **RAG(检索增强生成)**4. **评估与监控**5. **智能代理(Agents)** 下一步计划 VMware Spring 团队 Mark …

亚马逊云科技推出Anthropic新一代模型

5月23日 亚马逊云科技宣布在Amazon Bedrock中推出Anthropic的最新一代模型Claude Opus 4和Claude Sonnet 4。这两款全新混合推理模型能够根据需求在快速响应和深度思考模式间灵活切换,为编码、高级推理和多步骤工作流领域带来全新标准。它们不仅能在复杂的长时间推理…

无人机开启未来配送新篇章

低空物流(无人机物流)是利用无人机等低空飞行器进行货物运输的物流方式,依托低空空域(通常在120-300米)实现快速、高效、灵活的配送服务。它是低空经济的重要组成部分,广泛应用于快递配送、医疗物资运输、农…

数据赋能(234)——数据管理——标准化原则

概述 标准化原则的重要性体现在确保数据的格式、结构和命名的一致性。这不仅可以提高数据的质量,还能促进数据的有效共享、交换和利用。以下是标准化原则的重要性的具体体现: 提高数据通用性:遵循数据标准和规范,确保不同系统、…

【Linux笔记】——线程池项目与线程安全单例模式

🔥个人主页🔥:孤寂大仙V 🌈收录专栏🌈:Linux 🌹往期回顾🌹: 【Linux笔记】——简单实习一个日志项目 🔖流水不争,争的是滔滔不息 一、线程池设计二…

28-FreeRTOS内核控制-延时-临界区

一、FreeRTOS的内核控制接口分析 1.1 函数taskYIELD 此函数用于进行任务切换,此函数本质上是一个宏。它允许当前任务主动放弃CPU使用权,将控制权转移给调度器,以便调度器可以选择另一个就绪任务运行。taskYIELD通常用于协作式多任务系统中&am…

NtfsLookupAttributeByName函数分析之和Scb->AttributeName的关系

第一部分: VOID FindFirstIndexEntry ( IN PIRP_CONTEXT IrpContext, IN PSCB Scb, IN PVOID Value, IN OUT PINDEX_CONTEXT IndexContext ) { 。。。。。。 // // Lookup the attribute record from the Scb. // if (!NtfsLookupAt…

关闭 Ubuntu 20.04 的 GNOME Shell和PulseAudio

一、GNOME Shell GNOME Shell 是 Ubuntu 20.04 默认的桌面环境管理器。关闭它会失去图形界面(回到纯终端模式),但可以节省内存和 CPU 资源。 方法 1:临时关闭(当前会话生效) sudo systemctl stop gdm #…