Web 平台 API / RxJS · 从 Observable 标准契约,到它解决的那些问题 / 冷与热:各跑一遍,还是共享一次执行 待审核 6 / 7
hot-cold · Subject / share / multicast

冷与热:各跑一遍,还是共享一次执行

同样是一条 Observable,订阅它可能触发两种截然不同的行为。cold(冷)流把 producer 逻辑攥在自己手里,每次 subscribe 都各跑一遍,每个订阅者拿到独立、互不相干的一份值。hot(热)流的值独立于订阅产生,多个订阅者共享同一次执行,晚订阅者会错过订阅前已经发出的值。分清冷热是避免「同一份网络请求被每个订阅者重复触发」这类问题的前提。

以下一律用 cold / hot 指这两种语义。要注意它与 replay 不是一回事:cold 的第二个订阅者拿到的是 producer 重新执行产生的新值,而不是旧值的回放;回放是 ReplaySubjectshareReplay 的职责,把已经发出过的值补给晚订阅者,机制正相反。

1 · cold 的独立执行

下面是一条 interval 形态的 cold 流。订阅者 A 在 t=0 订阅,B 在 t=3 订阅。因为是 cold,B 的订阅会让 producer 从头重新跑一遍,所以 A、B 各拿到一份值序列,都从 0 开始计,彼此毫无关系。

图 1-1 · 两个订阅者各自独立地从自己的订阅时刻起步计数。可推进时间游标,注意两条轨道的值都从 0 开始。

2 · hot 的共享执行

换成 hot 流(如 Subject):源在后台持续发值,产生过程与谁订阅无关。A 在 t=0 就订阅,收到全部;B 在 t=3 才订阅,只能收到 t3t \ge 3 的值,t=0,1,2 那三个在它订阅前就已发出并溜走。A、B 共享的是同一串值。

图 2-1 · 同一串值被两个订阅者共享,B 轨道上 t<3t < 3 的三格是空缺的。可推进时间游标对照两条轨道的对齐关系。

3 · Subject 的双重身份

hot 流的典型载体是 Subject:它既是 Observer(可以 subject.next(v) 手动推值),又是 Observable(可以被 subscribe),因此天然是「一处推、多处收」的多播中枢。

图 3-1 · A 先订阅,推 12;之后 B 才订阅,再推 3。可单步推进观察两个订阅者各收到什么。

结果是 A 收到 1, 2, 3,B 只收到 3。B 订阅前推出的 12 对它来说已经错过,这就是 hot 的语义。把一条 cold 流经 share() 包一层,内部就是用 Subject 把单次执行多播出去,从而避免每个订阅者都重跑一遍 producer。

4 · 两种语义的对照

「晚订阅是否错过」一栏是选型时最实际的判据;要让晚订阅者补到旧值,需要的是 shareReplay 而非 share
维度 cold hot
谁触发执行 每次 subscribe 各触发一次 执行独立于订阅,后台已在跑
订阅者是否共享 不共享,各跑一份 共享同一次执行
晚订阅是否错过 不会,producer 重新执行 会,只收订阅后的值
典型例子 of / interval / 包着请求的流 Subject / DOM 事件 / share() 之后

建议 · 冷热不是 Observable 的固有标签,而由「值从哪来」决定:producer 在 subscribe 内部新建资源(定时器、请求)就是 cold;producer 只是转发一个早已存在、独立运行的源(Subject、DOM 元素)就是 hot。按这条判据,创建一页的 fromEvent 虽然惰性却是 hot。把冷热选择用在真实场景见落地