编码

关于 RxJS 的 iif 的一些思考

by Cheng, 2024-06-22


在写业务逻辑的时候,经常会遇到一些场景,根据某些先决条件进行决策调用后续的接口请求,例如在处理订单支付的时候,如果是新的订单,那么需要调用订单支付接口;如果是已有的订单,那么需要调用重新支付接口,因为这个时候订单已经创建好了,只需要重新支付即可。

要进行编码的时候,自然而然想到的就是使用 iif 。因为 iif 会根据第一个参数的返回结果,返回一个 Observable ,这个 Observable 会订阅其余两个参数中的一个。那么就会写出如下的实现。

processOrder$.pipe(
  switchMap((isNewOrder) => iif(
    () => isNewOrder === true,
    from(fetch('/order')),
    from(fetch('/pay')),
  )),
).subscribe();

但是上述的代码并不会像想象中的那样执行,观察网络请求,可以看到两个请求都被执行了。

network_request.webp

深挖一下 iif 的实现

export function iif<T, F>(condition: () => boolean, trueResult: ObservableInput<T>, falseResult: ObservableInput<F>): Observable<T | F> {
  return defer(() => (condition() ? trueResult : falseResult));
}

可以观察到 iif 仅仅是对 defer 的简单封装和调用。那么 defer 又做了什么呢?

export function defer<R extends ObservableInput<any>>(observableFactory: () => R): Observable<ObservedValueOf<R>> {
  return new Observable<ObservedValueOf<R>>((subscriber) => {
    from(observableFactory()).subscribe(subscriber);
  });
}

可以看到 defer 创建了一个 Observable ,将参数执行返回的 Observable 变成了高阶 Observable ,在被订阅的时候变成 Observerable,类似 switchMap 的效果。

iif 是函数,将函数调用作为参数传递给函数的时候,那么函数首先会被调用,这也是为什么,上述代码均被执行的原因。既然知道了原因,那么解决方法就接踵而来。核心思想是保证相关的路径只有在被需要的时候被执行。最简单的方法就是使用三元运算符,或者 if/else

processOrder$.pipe(
  switchMap((isNewOrder) => 
    isNewOrder === true ? 
    from(fetch('/order')) :
    from(fetch('/pay')),
  )),
).subscribe();

如果想要代码更为优雅坚持使用 iif 呢?通过上面关于 defer 实现的分析,可以利用 defer 来实现。

of(true).pipe(
  switchMap((result) => iif(
    () => result === true,
    defer(() => from(fetch('/pay'))),
    defer(() => from(fetch('/order'))),
  )),
).subscribe();

虽然两个 defer函数调用作为 iif 参数,在传参的那一刻被调用了,但是对于包裹的内部 Observerblefrom 的调用被推迟到了被订阅的那一刻,所以只有真正被订阅的那个请求才会被执行。

RxJSJavaScriptTypeScript

作者: Cheng

2025 © typecho & elise & Cheng