- 后端
【免费下载链接】RxJS
The Reactive Extensions for JavaScript
本文基于当前仓库 doc/api/core/operators/if.md 编写,围绕 RxJS v4 静态工厂方法
Rx.Observable.if(以及已废弃别名ifThen)展开。文章完整继承原文档的签名、参数、返回值与示例,并进一步结合 if.js 源码、底层 defer.js 实现 与 if.js 单元测试 进行深度剖析,帮助读者掌握条件选择 Observable 的惰性求值机制、Promise/Scheduler 兼容行为及正确的测试验证方法。
一、Rx.Observable.if是什么
Rx.Observable.if是 RxJS v4 提供的一个静态工厂操作符,用于根据一个条件函数的返回值,在两条可观察序列(Observable)之间做二选一:条件为true时订阅thenSource,为false时订阅elseSource。
它的应用场景非常典型:在响应式数据流中,常常需要根据运行时的状态(例如用户是否登录、配置开关是否开启、某个标志位是否为真)来决定订阅哪条数据流,而Rx.Observable.if恰好把这种"条件路由"封装成了声明式的 Observable,从而可以继续链式组合其他操作符。
在 RxJS v4 中,Rx.Observable.if还有一个面向 IE9 以下老浏览器的别名ifThen,但该别名已在文档中被明确标记为DEPRECATED(已废弃),新代码应直接使用Rx.Observable.if。
版本说明:本仓库是 RxJS v4 时代的代码(readme.md),本文所有签名、行为与源码引用均以仓库内实际内容为准。
二、方法签名与参数详解
原文档给出了两种调用形式:
Rx.Observable.if(condition, thenSource, [elseSource]) Rx.Observable.ifThen(condition, thenSource, [elseSource]) // DEPRECATED对应的 TypeScript 类型定义位于 ts/core/linq/observable/if.ts,并在 ts/rx.experimental.d.ts 与 ts/rx.all.d.ts 等声明文件中导出:
if<T>(condition: () => boolean, thenSource: ObservableOrPromise<T>, elseSourceOrScheduler?: ObservableOrPromise<T> | IScheduler): Observable<T>;各参数含义如下:
| 参数 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
condition | Function | 必填 | 决定运行thenSource还是elseSource的条件函数,返回布尔值 |
thenSource | Observable/Promise | 必填 | 当condition返回true时将被运行的可观察序列 |
elseSource | Observable/Promise/Scheduler | 可选 | 当condition返回false时将被运行的可观察序列;如果未提供,则默认使用Rx.Observable.Empty(可指定调度器) |
需要注意第三参数的"双态"设计:从源码 src/core/linq/observable/if.js 可以看出,内部参数名就叫elseSourceOrScheduler——它既可以是一个 Observable/Promise,也可以是一个 Scheduler(此时表示"用该调度器产生一个空序列作为 else 分支"),这一点在下一节结合源码详述。
三、返回值:惰性求值的条件选择序列
Rx.Observable.if返回一个 Observable,它"是"thenSource或elseSource中的某一个。但这里的"是"有一个非常重要的机制——惰性求值(deferred evaluation)。
先看返回值描述:(Observable): The generated sequence(生成的序列)。
从源码实现看,if内部其实是通过observableDefer(即Rx.Observable.defer)包装的:
// src/core/linq/observable/if.js Observable['if'] = function (condition, thenSource, elseSourceOrScheduler) { return observableDefer(function () { elseSourceOrScheduler || (elseSourceOrScheduler = observableEmpty()); isPromise(thenSource) && (thenSource = observableFromPromise(thenSource)); isPromise(elseSourceOrScheduler) && (elseSourceOrScheduler = observableFromPromise(elseSourceOrScheduler)); // Assume a scheduler for empty only typeof elseSourceOrScheduler.now === 'function' && (elseSourceOrScheduler = observableEmpty(elseSourceOrScheduler)); return condition() ? thenSource : elseSourceOrScheduler; }); };而observableDefer的底层实现在 src/core/linq/observable/defer.js:
Defer.prototype.subscribeCore = function (o) { var result = tryCatch(this._f)(); if (result === errorObj) { return observableThrow(result.e).subscribe(o);} isPromise(result) && (result = observableFromPromise(result)); return result.subscribe(o); };由此可以得到三条关键推论(均有源码依据):
- 每次订阅时重新求值:
condition工厂函数是在每次有新的观察者订阅时才被调用的(见Defer.prototype.subscribeCore中的this._f()),而不是在创建if序列时求值。这意味着条件可以在两次订阅之间发生变化,同一if序列被多次订阅时可能选中不同的分支。 - 工厂抛错会转为错误序列:如果
condition函数内部抛出异常,defer会通过tryCatch捕获(result === errorObj),并把该错误包装成observableThrow(result.e)发给订阅者,而不是让异常逃逸到调用栈外——这属于"错误即数据"的响应式设计。 - 结果若是 Promise 会被自动转换:无论
condition返回的是 Observable 还是 Promise,defer都会用observableFromPromise统一转换成 Observable(源码第 11 行)。
四、默认 else 分支行为与 Scheduler 参数
原文档明确指出:如果elseSource未提供,"it defaults to Rx.Observable.Empty with the specified scheduler"(默认使用Rx.Observable.Empty,可指定调度器)。
结合源码 src/core/linq/observable/if.js 的 4 行预处理逻辑:
elseSourceOrScheduler || (elseSourceOrScheduler = observableEmpty()); // 未传 else 分支 → 空序列 isPromise(thenSource) && (thenSource = observableFromPromise(thenSource)); // Promise → Observable isPromise(elseSourceOrScheduler) && (elseSourceOrScheduler = observableFromPromise(elseSourceOrScheduler)); typeof elseSourceOrScheduler.now === 'function' && (elseSourceOrScheduler = observableEmpty(elseSourceOrScheduler)); // Scheduler → 空序列可以总结出第三参数的三种传法及对应行为:
| 第三参数传法 | 行为 |
|---|---|
不传(undefined/null) | else 分支默认为Rx.Observable.Empty(立即完成,不发射任何值) |
传Observable | 直接作为 else 分支 |
传Promise | 先通过observableFromPromise转为 Observable,再作为 else 分支 |
传Scheduler(特征:对象上有now函数) | 用该 Scheduler 构造Rx.Observable.Empty(elseSourceOrScheduler)作为 else 分支 |
这里判断 Scheduler 的特征是typeof elseSourceOrScheduler.now === 'function'——因为 RxJS v4 中所有 Scheduler 都实现了now()方法,这与仓库内 src/concurrency/scheduler.js 中 Scheduler 基类的设计一致。observableEmpty(scheduler)的实现位于 src/modular/observable/empty.js(模块化版本),其行为是:使用immediate调度器时直接同步发送OnCompleted,否则通过给定调度器调度完成通知。
五、完整示例(可直接运行)
原文档给出了两个可直接在浏览器或 Node 环境运行的示例,完整继承如下。
5.1 仅使用 then 分支(省略 elseSource)
var shouldRun = true; var source = Rx.Observable.if( function () { return shouldRun; }, Rx.Observable.return(42) ); var subscription = source.subscribe( function (x) { console.log('Next: ' + x); }, function (err) { console.log('Error: ' + err); }, function () { console.log('Completed'); }); // => Next: 42 // => Completed此时condition返回true,订阅Rx.Observable.return(42);由于未提供 else 分支,如果条件改为false,将走Rx.Observable.Empty分支,观察者只会收到Completed,不会收到任何Next。
5.2 使用 elseSource 提供兜底分支
var shouldRun = false; var source = Rx.Observable.if( function () { return shouldRun; }, Rx.Observable.return(42), Rx.Observable.return(56) ); var subscription = source.subscribe( function (x) { console.log('Next: ' + x); }, function (err) { console.log('Error: ' + err); }, function () { console.log('Completed'); }); // => Next: 56 // => Completed这里condition返回false,于是选择 else 分支Rx.Observable.return(56),观察者收到Next: 56后序列完成。
5.3 传 Promise 与 Scheduler 的进阶示例
基于源码确认的 Promise/Scheduler 兼容行为(见第四节),还可以这样写:
// 第三参数传 Promise:else 分支来自 Promise 结果 var source = Rx.Observable.if( function () { return false; }, Rx.Observable.return(1), Promise.resolve(2) // 会被 observableFromPromise 自动转换 ); // 第三参数传 Scheduler:else 分支为指定调度器上的空序列 var source2 = Rx.Observable.if( function () { return false; }, Rx.Observable.return(1), Rx.Scheduler.default // 等价于 Rx.Observable.empty(Rx.Scheduler.default) );说明:
thenSource同样支持传入 Promise,源码第 17 行会对它做同样的转换处理。
六、源码实现深度解析:if与defer的调用链
Rx.Observable.if的完整实现只有十余行(src/core/linq/observable/if.js),它的设计精髓在于组合defer实现惰性路由。整体调用链如下:
Rx.Observable.if(condition, then, else) │ ▼ observableDefer(factory) // 创建 Defer 实例(继承 ObservableBase) │ ▼ observer 订阅 Defer │ ▼ Defer.subscribeCore(o) // src/core/linq/observable/defer.js ├─ tryCatch(factory)() // 此刻才真正执行 factory │ └─ factory 内部: │ ├─ 补默认 else 为空序列 │ ├─ Promise → observableFromPromise │ ├─ Scheduler → observableEmpty(scheduler) │ └─ return condition() ? thenSource : elseSource ├─ 若 factory 抛错 → observableThrow(e).subscribe(o) └─ 订阅最终选中的那条序列这个设计的价值在于:
- 分支选择被推迟到订阅时刻,与
Rx.Observable.defer的"工厂函数在每次订阅时被调用"语义完全一致,因此if天然支持条件随时间变化; - 错误被纳入数据流,
condition抛出的异常不会破坏订阅流程,而是以onError通知的形式传给观察者(对应测试If Throw中的onError(200, error)断言); - 与 Promise 生态无缝衔接,两个分支都允许是 Promise,省去手动调用
Rx.Observable.fromPromise的样板代码。
可以推断,if与 src/core/linq/observable/case.js(Rx.Observable.case,模块化版本注册于 src/modular/index.js)属于同一族"条件路由"操作符:if是二选一,case是多选一(基于switch语义按 key 选择分支),二者都基于同样的惰性 defer 机制。
七、单元测试验证:条件路由的七种行为
仓库为if提供了完整的单元测试,位于 tests/observable/if.js,使用 QUnit +TestScheduler虚拟时间调度。七个用例覆盖了条件路由的典型边界行为:
| 测试用例 | 场景 | 断言要点 |
|---|---|---|
If True | condition 恒为true | 只订阅xs(subscribe(200, 300)),ys完全不被订阅 |
If False | condition 恒为false | 只订阅ys(subscribe(200, 400)),xs完全不被订阅 |
If Throw | condition 抛出异常 | 观察者收到onError(200, error),两个分支均未被订阅——错误被 defer 捕获并转为错误序列 |
If Dispose | 订阅中途释放 | 已选中的xs收到subscribe(200, 1000),未选中的ys无订阅 |
If Default Completed | 条件 150ms 后由 false 变 true,未传 else | 条件为 false 时走默认空分支;条件变 true 后订阅xs并完整收到onNext/onCompleted |
If Default Error | 同上,但xs中途报错 | 错误以onError(440, error)完整透传 |
If Default Other | 条件 150ms 后由 true 变 false | 走默认空分支,观察者仅在订阅点onCompleted(200)立即完成 |
其中If Default Completed等用例特别验证了第四节的"默认 else 分支"行为:当 else 未提供且条件为 false 时,结果序列等价于空序列(立即完成、无值发射),并且条件在 150ms 虚拟时间点改变后,同一if序列订阅时选择了新的分支——这正是惰性求值机制的测试级证据。
这些用例同时示范了 RxJS v4 官方推荐的测试范式:用Rx.TestScheduler.createHotObservable构造热序列、用Rx.ReactiveTest.onNext/onError/onCompleted描述时间轴、用startScheduler驱动订阅、用results.messages.assertEqual断言消息序列、用xs.subscriptions.assertEqual断言订阅/释放时机。相关工具可在 tests/helpers/reactiveassert.js 与 doc/api/testing/testscheduler.md 中进一步了解。
八、实践要点与注意事项
综合文档与源码,使用Rx.Observable.if时应注意:
- 不要对
if的返回序列做"一次性求值"的假设:condition在每次订阅时都会重新执行,如果条件依赖可变状态,不同订阅者可能进入不同分支(这正是If Default系列测试展示的能力)。 condition是同步调用的:它在订阅时刻同步执行并立即返回分支选择结果,不涉及调度;如需延迟求值,可组合Rx.Observable.defer或调度器自行控制。- 第三参数的语义随类型变化:传 Scheduler 表示"空序列 + 该调度器",传 Promise 表示"异步 else 分支",传 Observable 表示"普通 else 分支",不传表示"立即完成的空分支"。
ifThen别名已废弃:原文档将其标记为 DEPRECATED,且当前仓库源码中已检索不到ifThen的实现(仅 doc/api/core/operators/if.md 中有历史说明),新代码请统一使用Rx.Observable.if。- 类型安全:TypeScript 用户可直接使用 ts/rx.experimental.d.ts 或 ts/rx.all.d.ts 中导出的
if<T>泛型签名,编译期即可约束thenSource与elseSource的元素类型一致。
九、获取方式与文件位置
原文档给出了该操作符在仓库中的位置与分发方式,以下均为本仓库内实际存在的路径:
- 核心实现:src/core/linq/observable/if.js(完整实现,含 JSDoc 示例)
- TypeScript 声明:ts/core/linq/observable/if.ts,并随 ts/rx.experimental.d.ts、ts/rx.all.d.ts、ts/rx.experimental.es6.d.ts 等声明文件对外导出
- 单元测试:tests/observable/if.js
- 依赖的底层操作符:src/core/linq/observable/defer.js(惰性工厂)、
observableEmpty(模块化版本见 src/modular/observable/empty.js)、observableFromPromise(见 src/core/linq/observable/frompromise.js) - 相关操作符:多路条件路由
Rx.Observable.case(模块化注册见 src/modular/index.js,测试见 tests/observable/case.js)
原文档提到的分发渠道为 NPM 包rx与 NuGet 包RxJS-Experimental(对应本仓库 nuget/RxJS-Experimental/RxJS-Experimental.nuspec);本仓库未内置dist构建产物目录,核心版源码直接面向打包与发布流程。if属于rx.experimental命名空间下的实验性操作符,因此在 ts/rx.experimental.d.ts 与 ts/rx.all.d.ts 中均有导出,而在精简版(lite)系列中不包含。
十、小结
Rx.Observable.if用不到二十行源码,把"条件路由"这一常见需求封装成了符合 Rx 语义的惰性 Observable:它依赖defer将分支选择推迟到订阅时刻、用tryCatch将条件异常转成错误通知、并兼容 Observable / Promise / Scheduler 三种 else 形态。配合 tests/observable/if.js 的七组测试,读者既能掌握它的正确用法,也能理解其底层求值时机,进而在自己的响应式代码中正确地组合使用if、case与defer。
- 后端
【免费下载链接】RxJS
The Reactive Extensions for JavaScript
相关推荐
RxJS 4 `Rx.Observable.case` 操作符完全指南:用条件分支动态选择数据源
RxJS 4 Rx.Observable.case 操作符完全指南:用条件分支动态选择数据源 导读 Rx.Observable.case (别名 switchC
后端Kafka-King终极指南:3分钟掌握可视化Kafka管理工具
Kafka King终极指南:3分钟掌握可视化Kafka管理工具 还在为复杂的Kafka命令行操作而烦恼吗?Kafka King作为一款现代化、实用的Kafka
后端RxJS 4 错误恢复操作符 `catch` 全面解析:`Rx.Observable.prototype.catch(second | handler)` 的用法、源码原理与测试验证
RxJS 4 错误恢复操作符 catch 全面解析: Rx.Observable.prototype.catch second | handler 的用法、源码
后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考