【问题标题】:Rxjs - Chain http calls with ObservablesRxjs - 使用 Observables 链接 http 调用
【发布时间】:2021-12-17 09:11:03
【问题描述】:

我想进行 3 个返回布尔值的 http 调用。 我想要的是只有在上一次调用的结果是true时才进行下一次调用

IE:
call1() -> 如果false,停止,如果true:
call2() -> 如果false,停止,如果true:
@987654329 @

现在以指示返回 false 的调用的方式读取结果。

我第一次尝试使用 switchMap:

    this.call1()
      .pipe(
        switchMap((r1) => (r1 ? this.call2() : of(false))),
        switchMap((r2) => (r2 ? this.call3() : of(false)))
      )
      .subscribe((r) => {
        console.log('inside subscribe:', r);
      });

现在在订阅中我得到的问题是:
'inside subscribe:' false
而且我不知道错误结果是来自 call1,2 还是 3。

所以我的解决方案是将结果保存在外部数组中,如下所示:

    // seriel execution with switchMap, we can use each result to the next call
    // + save all results
    const results = [false, false, false];
    this.call1()
      .pipe(
        // save stage1 result
        tap(r1 => results[0] = r1),
        // if true, make call2, else return false
        switchMap((r1) => (r1 ? this.call2() : of(false))),
        // save stage2 result
        tap(r2 => results[1] = r2),
        // if true, make call3, else return false
        switchMap((r2) => (r2 ? this.call3() : of(false))),
        // save stage3 result
        tap(r3 => results[2] = r3),
      )
      .subscribe((r) => {
        console.log('inside subscribe:', r); // r is the latest value results[2]
        console.log('all results:', results) // array of all stages [true,false,false]
      });

  }

它似乎有效,但也许还有另一种无需外部工作的方法?仅使用 rxjs 运算符?

StackBlitz 链接:
https://stackblitz.com/edit/angular-rxjs-playground-zjnh7u?devtoolsheight=33&file=app/app.component.ts

【问题讨论】:

  • 为什么返回值很重要?

标签: angular rxjs


【解决方案1】:

也许这样的东西可以工作

this.call1()
  .pipe(
    switchMap((r1) => (r1 ? this.call2() : 
             of(false).pipe(tap(() => console.log('r1 returns false')),
    switchMap((r2) => (r2 ? this.call3() : 
             of(false).pipe(tap(() => console.log('r2 returns false'))),
    tap(respOfR3 => if(!respOfR3) {console.log('r3 returns false')})
  )
  .subscribe((r) => {
    console.log('inside subscribe:', r);
  });

同时考虑你的第一个 Observable 通知的布尔序列隐含地包含你正在寻找的信息。事实上,您遇到的第一个 false 的位置反映了第一个返回 false 的调用。

【讨论】:

    【解决方案2】:

    我会将源附加到结果 - 最简单的是元组 [number, boolean],其中元组/数组中的第一个元素是源。 switchMap 解决方案的一个变体是:

        this.call1()
          .pipe(
            map((result) => [1, result]),
            switchMap((r1) => (r1[1] ? this.call2().pipe(map((result) => [2, result])) : of(r1))),
            switchMap((r2) => (r2[1] ? this.call3().pipe(map((result) => [3, result])) : of(r2)))
          )
          .subscribe((r) => {
            console.log('inside subscribe:', r);
          });
    

    这有一个模式,有点冗长,我们可以做得更好:

        function attachSource(source: number, target: Observable<boolean>): Observable<[number, boolean]> {
          return target.pipe(map((result) => [source, result]));
        }
    
        attachSource(1, this.call1())
          .pipe(
            switchMap((r1) => (r1[1] ? attachSource(2, this.call2()) : of(r1))),
            switchMap((r2) => (r2[1] ? attachSource(3, this.call3()) : of(r2)))
          )
          .subscribe((r) => {
            console.log('inside subscribe (try2):', r);
          });
    

    如果我们再概括一点,给出以下内容:

        function attachSource<T>(source: number, target: () => Observable<T>): Observable<[number, T]> {
          return target().pipe(map((result) => [source, result]));
        }
    
        function series<T>(failed: (arg: T) => boolean, ...factories: (() => Observable<T>)[]): Observable<[number, T]> {
          return factories.reduce((agr, cur, index) => {
            if (index === 0) {
              return attachSource(index+1, cur);
            } else {
              return agr.pipe(switchMap((prevResult) => failed(prevResult[1]) ? of(prevResult) : attachSource(index+1, cur)));
            }
          }, empty<[number, T]>());
        }
    

    可以这么简单:

        series((x: boolean) => x, this.call1.bind(this), this.call2.bind(this), this.call3.bind(this))
          .subscribe((r) => {
            console.log('inside subscribe (try3):', r);
          });
    

    【讨论】:

      【解决方案3】:

      这是一个非常干净的解决方案,使用扩展运算符进行递归调用

      const results = [false, false, false];
      let count = 0;
      this.call().pipe(
          tap(response => {
            results[count] = response
            count++
            }
          ),
          expand((response) => response && count < 3
                     ? this.call()
                     : EMPTY
          )
      ).subscribe((r) => {
          console.log('inside subscribe:', r); // r is the latest value results[2]
          console.log('all results:', results) // array of all stages [true,false,false]
      });
      

      而且你并不需要多个调用函数(call1、call2、call3),一个就足够了

      call(): Observable<boolean> {
       let bool: boolean = this.randBool();
       return of(bool);
      }
      
      private randBool(): boolean {
        return Math.random() < 0.5
      }
      

      https://stackblitz.com/edit/angular-rxjs-playground-zvfpay?file=app/app.component.ts

      【讨论】:

      • 如果这些调用是调用不同端点的 3 个不同函数怎么办?
      • 在这种情况下,您可以将参数传递给调用函数,使其像 (url, payload ...) 一样动态,但对于这个问题,exapnd 是最好的解决方案,因为 3 个调用是相同的
      猜你喜欢
      • 2017-01-06
      • 2016-05-18
      • 2016-08-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-09-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多