冷与热:各跑一遍,还是共享一次执行
同样是一条 Observable,订阅它可能触发两种截然不同的行为。cold(冷)流把 producer 逻辑攥在自己手里,每次 subscribe 都各跑一遍,每个订阅者拿到独立、互不相干的一份值。hot(热)流的值独立于订阅产生,多个订阅者共享同一次执行,晚订阅者会错过订阅前已经发出的值。分清冷热是避免「同一份网络请求被每个订阅者重复触发」这类问题的前提。
以下一律用 cold / hot 指这两种语义。要注意它与 replay 不是一回事:cold 的第二个订阅者拿到的是 producer 重新执行产生的新值,而不是旧值的回放;回放是 ReplaySubject 与 shareReplay 的职责,把已经发出过的值补给晚订阅者,机制正相反。
1 · cold 的独立执行
下面是一条 interval 形态的 cold 流。订阅者 A 在 t=0 订阅,B 在 t=3 订阅。因为是 cold,B 的订阅会让 producer 从头重新跑一遍,所以 A、B 各拿到一份值序列,都从 0 开始计,彼此毫无关系。
0 开始。2 · hot 的共享执行
换成 hot 流(如 Subject):源在后台持续发值,产生过程与谁订阅无关。A 在 t=0 就订阅,收到全部;B 在 t=3 才订阅,只能收到
的值,t=0,1,2 那三个在它订阅前就已发出并溜走。A、B 共享的是同一串值。
3 · Subject 的双重身份
hot 流的典型载体是 Subject:它既是 Observer(可以 subject.next(v) 手动推值),又是 Observable(可以被 subscribe),因此天然是「一处推、多处收」的多播中枢。
1、2;之后 B 才订阅,再推 3。可单步推进观察两个订阅者各收到什么。结果是 A 收到 1, 2, 3,B 只收到 3。B 订阅前推出的 1、2 对它来说已经错过,这就是 hot 的语义。把一条 cold 流经 share() 包一层,内部就是用 Subject 把单次执行多播出去,从而避免每个订阅者都重跑一遍 producer。
4 · 两种语义的对照
| 维度 | cold | hot |
|---|---|---|
| 谁触发执行 | 每次 subscribe 各触发一次 | 执行独立于订阅,后台已在跑 |
| 订阅者是否共享 | 不共享,各跑一份 | 共享同一次执行 |
| 晚订阅是否错过 | 不会,producer 重新执行 | 会,只收订阅后的值 |
| 典型例子 | of / interval / 包着请求的流 |
Subject / DOM 事件 / share() 之后 |
建议 · 冷热不是 Observable 的固有标签,而由「值从哪来」决定:producer 在 subscribe 内部新建资源(定时器、请求)就是 cold;producer 只是转发一个早已存在、独立运行的源(Subject、DOM 元素)就是 hot。按这条判据,创建一页的
fromEvent 虽然惰性却是 hot。把冷热选择用在真实场景见落地。