落地:这些问题,用流怎么优雅解决
前面把 契约、operator、flatten 都拆过了。这一页把它们对上真实工程痛点——每个都是一段命令式写法会写得很别扭、而用流几行就说清的场景。
1 · autocomplete:防抖 + 消除请求竞态
输入框每次改动都要查询后端。命令式写法有两个坑:抖动(每敲一个字符都发请求)与竞态(先发的慢请求晚返回,把过期结果盖在新结果上)。流的解法:。下面左右对照 naive(直接发)与 switchMap(取消旧的);故意让越短的词延迟越久,好让竞态现形。
2 · 拖拽:mousedown → switchMap(mousemove 直到 mouseup)
拖拽是三个事件流的组合:一次 mousedown 开启一段 mousemove,直到 mouseup 结束。用 switchMap 把每次按下映射成一条「移动流」,再用 takeUntil(mouseup$) 给它自动收尾——不用手动 add /
removeEventListener,也不会漏解绑。在下面方块上按住拖动:
3 · retry:失败后按退避延迟重订阅
Observable 的可取消 / 可重订阅让重试变得直接:出错时不必手写循环与计时器,retry({ count, delay }) 会在 error 后延迟一段再重新订阅上游——延迟逐次加倍即指数退避。下面这个请求前两次失败、第三次成功:
4 · 问题 → operator 速查
| 要解决的问题 | operator 组合 |
|---|---|
| 输入抖动:每敲一下都发请求 | debounceTime / auditTime / throttleTime |
| 重复值:内容没变也重复请求 | distinctUntilChanged |
| 请求竞态:过期响应盖掉新结果 | switchMap |
| 并行任务收集全部结果 | mergeMap + toArray / forkJoin |
| 严格按序处理 | concatMap |
| 防连点:提交中忽略新点击 | exhaustMap |
| 事件的生命周期 / 解绑 | takeUntil / take / takeWhile |
| 失败自动重试 + 退避 | retry({ count, delay }) / retryWhen |
| 共享一次请求给多个订阅者 | share / shareReplay |
这些场景的共同点:「随时间到来的事件」被当成一条可组合、可取消的流。抖动用 debounceTime 合并、竞态用 switchMap 取消、生命周期用 takeUntil 收尾、失败用 retry 重订阅——全都建立在
最初那个契约的三个能力(惰性、多值、可取消)之上。这就是 RxJS 真正解决的问题。