类似于exhaustMap的运算符,但它会记住源中最后一个跳过的值并在最后执行它

时间:2018-08-13 12:02:55

标签: rxjs reactivex

我需要一个类似于exahustMap的运算符,但是该运算符会记住最后一个被跳过的可观察对象,并在当前可观察对象完成后执行它。

例如,考虑exhaustMap的大理石图:

exhaustMap marble diagram

在我的情况下,发出蓝色值之后,将跟随三个值50。当然,在这种情况下,它看起来像concatMap,但是如果在3和之间还有一个4 5,它不会反映在输出中。

我设法编写了自己的运算符,类似于实现exhaustMap的方式:

function exhaustLatestMap<T, R>(project: (value: T) => Subscribable<R>): OperatorFunction<T, R> {
    return source => new Observable<R>(observer => 
        source.subscribe(new ExhaustLatestMapOperatorSubscriber(observer, project)));
}

class ExhaustLatestMapOperatorSubscriber<T, R> implements Observer<T> {

    constructor(
        private observer: Subscriber<R>,
        private project: (value: T) => Subscribable<R>) { }

    innerSub: AnonymousSubscription = null;
    latestValue: T;

    next(value: T) {
        this.processNext(value);
    }

    error(err) {
        this.observer.error(err);
    }

    complete() {
        this.observer.complete();
    }

    private processNext(value: T) {
        this.latestValue = value;
        if (!this.innerSub) {
            this.innerSub = this.project(value).subscribe({
                next: v => this.observer.next(v),
                error: err => {
                    this.observer.error(err);
                    this.endInnerSub(value)
                },
                complete: () => {
                    this.endInnerSub(value);
                }
            });
        }
    }

    private endInnerSub(value: T) {
        this.innerSub.unsubscribe();
        this.innerSub = null;
        if (this.latestValue !== value) {
            this.processNext(this.latestValue);
        }
    }
}

但是我想知道是否有一种方法可以通过重用和合并现有的运算符来实现。有什么想法吗?

1 个答案:

答案 0 :(得分:1)

可以仅使用内置工厂和操作员来实现它。但是,对于AFAICT,如果不管理某些按预订状态就无法完成。

幸运的是,defer工厂功能使管理每个订阅状态变得相对简单和安全。并且,除了帮助管理按预订状态之外,defer还可作为一种机制,用于在观察到可观察对象的订阅时间得到通知。

另一种实现方式:

const {
  concat,
  defer,
  EMPTY,
  merge,
  of
} = rxjs;

const {
  delay,
  mergeMap,
  tap
} = rxjs.operators;

const exhaustMapLatest = project => source => defer(() => {
  let latestValue;
  let hasLatestValue = false;
  let isExhausting = false;
  const next = value => defer(() => {
    if (isExhausting) {
      latestValue = value;
      hasLatestValue = true;
      return EMPTY;
    }
    hasLatestValue = false;
    isExhausting = true;
    return project(value).pipe(
      tap({ complete: () => isExhausting = false }),
      s => concat(s, defer(() => hasLatestValue ?
        next(latestValue) :
        EMPTY
      ))
    );
  });
  return source.pipe(mergeMap(next));
});

const source = merge(
  of(0).pipe(delay(0)),
  of(1000).pipe(delay(1000)),
  of(1100).pipe(delay(1100)),
  of(1200).pipe(delay(1200)),
  of(2000).pipe(delay(2000))
);

source.pipe(
  exhaustMapLatest(value => merge(
    of(`${value}:0`).pipe(delay(0)),
    of(`${value}:150`).pipe(delay(150)),
    of(`${value}:300`).pipe(delay(300))
  ))
).subscribe(value => console.log(value));
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://unpkg.com/rxjs@6/bundles/rxjs.umd.min.js"></script>

此实现与您的实现在行为上有一些区别:

  • 此实现使用hasLatestValue标志而不是相等性检查,因此,如果最新值等于初始值,则仍会进行投影。
  • 使用此实现,如果结果可观察的取消订阅的订阅者,则对计划可观察的订阅也将取消订阅。在实施过程中,内部订阅将保持订阅状态-AFAICT,直到预计的可观察完成或错误为止。

我不主张应以这种方式实施。答案只是为了显示替代实施方式。