代码之家  ›  专栏  ›  技术社区  ›  Guichi

RXJS switchmap需要返回订阅的Observable

  •  1
  • Guichi  · 技术社区  · 6 年前

    这里是要求:

    当单击开始按钮时,每100毫秒发出事件x次,每个发出对应一个UI更新。当x次Emit完成时,它将触发最终的UI更新,看起来很简单,对吗?

    这是我的代码:

    const start$ = fromEvent(document.getElementById('start'), 'click')
    const intervel$ = interval(100)
        .pipe(
            take(x),
            share()
        )
    var startLight$ = start$
        .pipe(
            switchMap(() => {
                intervel$
                    .pipe(last())
                    .subscribe(() => {
                        // Update UI
                    })
                return intervel$
            }),
            share()
        )
    startLight$
        .subscribe(function (e) {
            //Update UI
        })
    

    显然,在里面订阅 switchMap 是反模式的,所以我尝试重构代码:

    const startInterval$ = start$
        .pipe(
            switchMapTo(intervel$),
        )
    startInterval$.pipe(last())
        .subscribe(() => {
            //NEVER Receive value
        })
    
    const startLight$ = startInterval$.pipe(share()) 
    

    问题是Intervel$stream是在内部生成的 开关图 并且不能在外部访问,您只能访问生成间隔$的流,即从未完成的start$流!

    是否有更聪明的方法来处理此类问题,或者这是 rxjs ?

    2 回复  |  直到 6 年前
        1
  •  1
  •   Guichi    6 年前

    你离得很近。在intervel$中使用last()只向下面的订阅发出最后一个。工作 StackBlitz . 以下是Stackblitz的详细信息:

    const start$ = fromEvent(document.getElementById('start'), 'click');
    const intervel$ = interval(100)
        .pipe(
            tap(() => console.log('update UI')), // Update UI here
            take(x),
            last()
        );
    
    const startInterval$ = start$
        .pipe( switchMapTo(intervel$));
    
    startInterval$
        .subscribe(() => {
            console.log('will run once');
        });
    

    更新

    如果您不想使用 tap() ,然后您只需进行第一次排放,然后用其中一个完成,就可以使start$完成。 take(1) first() . Here is a new StackBlitz 展示这一点。

    const start$ = fromEvent(document.getElementById('start'), 'click')
        .pipe(
          first()
        );
    const intervel$ = interval(100)
        .pipe( 
          take(x) 
        );
    
    const startInterval$ = start$
        .pipe( 
          switchMapTo(intervel$)
        );
    
    startInterval$
        .subscribe(
          () => console.log('Update UI'),
          err => console.log('Error ', err),
          () => console.log('Run once at the end')
        );
    

    这种方法(或任何完成可观察的方法)的缺点是,一旦完成,它就不会被重用。例如,在新的stackblitz中多次点击按钮是行不通的。使用哪种方法(第一种可以反复单击或完成的方法)取决于您需要的结果。

    还有另一个选择

    创建两个 intervel$ 一个用于中间用户界面更新,另一个用于最终用户界面更新。将它们合并在一起,只在订阅中进行UI更新。 StackBlitz 对于这个选项

    代码:

    const start$ = fromEvent(document.getElementById('start'), 'click')
    const intervel1$ = interval(100)
        .pipe( 
          take(x)
        );
    const intervel2$ = interval(100)
        .pipe(
          take(x+1),
          last(),
          mapTo('Final')
        );
    
    const startInterval$ = start$
        .pipe( 
          switchMapTo(merge(intervel1$, intervel2$))
        );
    
    startInterval$
        .subscribe(
            val => console.log('Update UI: ', val)
        );
    

    一种更惯用的方法,与前一种逻辑相同(由贵池)

    import { switchMapTo, tap, take, last, share, mapTo } from 'rxjs/operators';
    import { fromEvent, interval, merge } from 'rxjs';
    
    const x = 5;
    
    const start$ = fromEvent(document.getElementById('start'), 'click');
    
    
    const intervel$ = interval(100);
    
    const intervel1$ = intervel$
      .pipe(
        take(x)
      );
    const intervel2$ = intervel1$
      .pipe(
        last(),
        mapTo('Final')
      );
    
    const startInterval$ = start$
      .pipe(
        switchMapTo(merge(intervel1$, intervel2$))
      );
    
    startInterval$
      .subscribe(
        val => console.log('Update UI: ', val)
      );
    

    反射

    原始问题的关键问题是“以不同的方式使用相同的可观察到的”,即在进度和最终阶段。所以 merge 是一个很好的逻辑模式来解决这类问题

        2
  •  1
  •   Fan Cheung    6 年前

    将更新逻辑放入 switchMap tap() ,tap将运行多次,只有最后一次发射将由subscribe()进行。

    const startInterval$ = start$
        .pipe(
            switchMap(()=>intervel$.pipe(tap(()=>//update UI),last()),
        )
    startInterval$
        .subscribe(() => {
    //    will run one time
        })