rxjs switchMap需要返回可观察的订阅

时间:2019-01-24 05:01:16

标签: rxjs observable

这里是要求:

单击“开始”按钮时,每100毫秒发出一次事件x,每次发出都对应一个UI更新。当x次发射完成时,它将触发最终的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 $流是在switchMap内部生成的,无法在外部访问,您只能访问生成interval $的流,即start $,它永远不会完成!

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

2 个答案:

答案 0 :(得分:1)

将您的更新逻辑放入switchMaptap()内,点击将运行多次,并且subscribe()仅获取最后一个发射

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

答案 1 :(得分:1)

您非常亲密。在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(),则只需通过仅获取第一个发射,然后完成take(1)first()即可使start $完成。 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')
    );

此方法(或完成Observable的任何方法)的缺点是,一旦完成,将无法重用。因此,例如,多次单击新StackBlitz中的按钮将不起作用。使用哪种方法(第一个可以反复单击或完成的方法)取决于您需要的结果。

又一个选择

创建两个intervel$可观察对象,一个用于中间UI更新,另一个用于最后一个。将它们合并在一起,仅在订阅中进行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)
    );

一种更惯用的方式,与上一个逻辑相同(由Guichi负责)

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是解决这类问题的一种相当不错的逻辑模式