RxJS 轮询策略:如何避免请求雪崩与数据丢失

ini 复制代码
import { Subject, timer } from 'rxjs';
import { exhaustMap, takeUntil } from 'rxjs/operators';

stopPolling$ = new Subject();
id = '121212';

a(){
	......
	this.queryStatus();
	......
	this.queryStatus();
};

queryStatus() {
	this.stopPolling$.next(null);
	timer(0, 1500)
		.pipe(exhaustMap(() => this.getStatus(id)), takeUntil(this.stopPolling$))
		.subscribe(res => {
			const { code, result } = res;
			if (code === '0' && result) {
				this.status = result;
				
				if (this.status === 1) {
					this.stopPolling$.next(null);
				}
			}
		});
};

switchMap会在新的请求到来时取消之前的请求,这可能导致如果请求时间超过间隔时间,后续的请求会不断取消前一个,结果可能没有请求成功。比如,假设定时器每1秒触发一次,而请求需要1.5秒完成。使用switchMap的话,每次新的定时触发都会取消之前的请求,导致请求永远无法完成。

而exhaustMap则是忽略新的请求,直到当前请求完成,这样即使间隔时间短于请求时间,也能保证每次只处理一个请求,但可能会漏掉一些请求。

核心区别

操作符 行为描述 适用场景
switchMap 每次新的源值(如 timer触发)会取消前一个未完成的请求,转而发起新请求。 需要"抢占式"请求(如搜索联想词输入时,只关心最后一次输入的结果)。
exhaustMap 忽略新的源值,直到当前请求完成。前一请求未完成时,后续请求会被"静默丢弃"。 需要"防抖"或"保证唯一请求"(如防止重复提交表单)。

场景分析

假设 timer 的间隔是 1500ms,而 getLoginStatus 请求需要 超过 1500ms 才能完成

  • 使用 ****switchMap
    • 每次 timer 触发时,会取消前一个未完成的请求。
    • 结果:所有请求都会被取消,最终没有请求能成功完成。
    • 示意图:
ini 复制代码
Timer: 0ms → [请求A开始] → 1500ms → Timer再次触发 → 取消A → [请求B开始] → ...
  • 使用 ****exhaustMap
    • 前一个请求未完成时,直接忽略后续的 timer 事件。
    • 结果:每隔至少一次请求时间(如 2000ms)才会完成一次请求,但能保证每次请求完整执行。
    • 示意图:
ini 复制代码
Timer: 0ms → [请求A开始] → 2000ms → 请求A完成 → Timer触发 → [请求B开始] → ...

为什么 switchMap 会导致请求无法完成?

  • 根本原因switchMap 的"取消前一个请求"特性,在请求时间 > 间隔时间时,会形成"请求永远无法完成"的死循环。
  • 代码示例
scss 复制代码
typescript

复制


timer(0, 1500).pipe(
  switchMap(() => this.getLoginStatus(...)) // 每次新 timer 触发,取消前一个请求
).subscribe(...);
    • 如果 getLoginStatus 始终需要 2000ms 完成,那么每次新的 timer 事件都会取消前一个请求,导致 没有一次请求能正常完成

如何选择操作符?

1. 需要确保请求完成(你的场景)

  • 使用 ****exhaustMap
    • 优点:保证每次请求都能完整执行,不会被中途取消。
    • 缺点:如果请求时间不稳定,可能导致轮询间隔不固定(例如请求耗时 2000ms,则实际轮询间隔为 2000ms + 你的 timer 间隔 1500ms)。
  • 改进方案:增加错误重试逻辑。
scss 复制代码
typescript

复制


timer(0, 1500)
  .pipe(
    exhaustMap(() => this.getLoginStatus(...).pipe(
      catchError(error => {
        console.error('请求失败,重试中...');
        return throwError(error); // 或者返回一个重试 Observable
      })
    )),
    takeUntil(this.stopPolling$)
  )
  .subscribe(...);

2. 需要严格按固定间隔轮询(不关心请求是否完成)

  • 使用 ****switchMap
    • 优点:能快速响应最新的请求(如用户主动触发的刷新)。
    • 缺点:如果请求时间不可控,可能导致频繁取消请求,甚至资源浪费。

关键结论

  • 如果请求时间可能超过轮询间隔
    • 必须使用 ****exhaustMap,否则 switchMap 会导致请求永远无法完成。
    • 需额外处理错误(如重试、超时机制),避免无限等待。
  • 如果请求时间可控且必须强制刷新最新数据
    • 使用 switchMap,但需确保请求能在间隔时间内完成。

扩展:如何保证请求间隔稳定?

如果希望请求严格按固定间隔执行(无论请求耗时),可以改用 interval + mergeMap,但需注意并发风险:

scss 复制代码
typescript

复制


interval(1500).pipe(
  mergeMap(() => this.getLoginStatus(...), 1) // 并发数限制为 1
).subscribe(...);
  • mergeMap 允许同时发起多个请求,但第二个参数 1 会限制并发数为 1(类似 exhaustMap)。
相关推荐
深念Y30 分钟前
我明白为什么B站没法在浏览器开直播了——Windows Chrome推流踩坑全记录
前端·chrome·webrtc·浏览器·srs·直播·flv
zhangxingchao41 分钟前
AI应用开发七:可以替代 RAG 的技术
前端·人工智能·后端
Sun@happy1 小时前
现代 Web 前端渗透——基础篇(1)
前端·web安全
希冀1231 小时前
【CSS学习第十一篇】
前端·css·学习
隔窗听雨眠1 小时前
doctype、charset、meta如何控制整个渲染流水线
java·服务器·前端
kyriewen2 小时前
写组件文档写到吐?我用AI自动生成Storybook,同事以后直接抄
前端·javascript·面试
excel2 小时前
🧠 Prisma 表名大写 vs SQL 导出小写问题深度解析(附踩坑与解决方案)
前端·后端
周淳APP2 小时前
【前端工程化原理通识:从源头到运行时的理论阐述】
前端·编译·打包·前端工程化
五点六六六2 小时前
你敢信这是非Native页面写出来的渐变效果吗🌝(底层原理解析
前端·javascript·面试
tedcloud1233 小时前
TradingAgents部署教程:打造AI量化分析工作流
服务器·前端·人工智能·系统架构·edge