契约:十几行手写一个 Observable
RxJS 的地基不是一大堆 operator,而是一个标准契约。它小到可以脱离库、用十几行手写出来:Observable 持有一段 producer 逻辑,只有在 subscribe 时才执行;producer 拿到一个 Observer(带 next / error / complete 的对象),通过它把值推出去;subscribe 返回一个
Subscription(一个能 unsubscribe 的句柄),用来随时取消。
这也是 TC39 Observable proposal 与 WICG 的 DOM Observable 提案(EventTarget.when())的形状。后者仍是实验特性,仅 Chromium 135+(2025-04)落地,Firefox 与 Safari 均未实现(兼容性据 MDN BCD,核对于
2026-08)。下面这十行就是整个 RxJS 的种子:
class Observable {
constructor(onSubscribe) {
this.onSubscribe = onSubscribe;
}
subscribe(observer) {
const teardown = this.onSubscribe(observer);
return { unsubscribe: () => teardown && teardown() };
}
}
1 · producer 把值推给 observer
整段 producer 在 subscribe 的那一刻才开始跑,这是 Observable 与立即执行的普通函数最不同的地方。每调用一次 observer.next(v),observer 就收到一个值、时间轴上多一颗 marble;最后 complete() 用竖线封口。
next 与 complete 的落点。2 · 事件序列的语法约束
一条流的事件序列受一条简单语法约束:可以推任意多个 next,之后至多用一个 error 或一个 complete 收尾。二者互斥,且一旦收尾就不再有任何事件。这条契约让下游可以放心地在 complete 或 error 里做清理。
1 2 3 | 合法:三个 next 后 complete
1 2 ✕ 合法:两个 next 后 error 收尾
| 合法:一个值都不推,直接 complete(空流)
1 | 2 非法:complete 之后不能再有 next
| ✕ 非法:complete 与 error 不能同时出现
本系列的图形记号用 ✕ 表示 error(与 SVG 渲染器画的叉一致)。RxJS TestScheduler 的 ASCII marble 记号里,error 写作 #、complete 写作 |,同帧的多个值用括号成组,如 of(1,2,3) 是 (123|) [1]。
3 · unsubscribe 与 teardown
producer 可以返回一个 teardown 函数,subscribe 把它包进 Subscription。调用 unsubscribe() 就会运行这个 teardown。它具体做什么完全由 producer 决定——可能停掉定时器、解掉事件监听,也可能什么都不做。这一点常被误传成「unsubscribe 会取消在途的网络请求」:能否真的 abort 取决于 teardown
里写了什么,fromFetch 会(它内部挂了 AbortController),而把一个 promise 包成 Observable 的写法不会,请求照跑到底,只是结果被丢弃。这在展平一页还会再遇到。
Promise 连这个选择都没有:一旦发起就无法取消。
interval$,可随时取消。可点取消观察 teardown 何时运行、marble 何时停止增长。
建议 · 对照 Promise 有三处本质差异。惰性:new Promise(fn) 里的 fn 立刻同步执行,而 Observable 的 producer 要 subscribe 才执行,且每次订阅各跑一遍。多值:Promise 只 resolve 一个值,Observable 能推任意多个 next。可取消:Promise 无法取消,Observable 用
unsubscribe 触发 teardown 收尾。四象限坐标系一页把这两者放进同一张表里比较。
4 · 参考文献
- RxJS Documentation. Marble Testing(ASCII marble 记号与同帧成组写法). ReactiveX.
- WICG. Observable(
EventTarget.when()提案). Web Incubator CG. - TC39. Observable proposal. Ecma International.