切换不浪费工作

时间:2017-05-12 16:40:07

标签: rxjs system.reactive reactive-programming

我有一个可观察的可观察量,其中内部可观察量每个都产生一个昂贵的计算值。我想要像Switch这样的行为,但是没有浪费工作(SwitchFrugal?):

  • 如果订阅了当前内部可观察对象,我希望继续接收它的值,直到它完成为止
    • 不应取消Switch
    • 中的订阅
  • 一旦当前的内部可观察量完成,最新的内部可观察量(如果当前有任何内容)应该被订阅

我一直在努力实现这种行为。这是否可以使用现有的运营商?或者这是否需要通过Observable.Create“从头开始”完成?

Marble Diagram

1 个答案:

答案 0 :(得分:1)

我这样做:

const subject = new Subject();
const start = Scheduler.async.now();

// Simulated source Observable
const source = Observable
    .timer(0, 900)
    .map(i => Observable.defer(() => {
        console.log('Subscribe inner Observable:', i);
        return Observable
            .timer(0, 400)
            .take(6)
            .map(j => i.toString() + ':' + j.toString());
    }))
    .share();

source
    .merge(subject
        .withLatestFrom(source)
        .map(([v, observable]) => {
            return observable;
        })
    )
    .exhaustMap(obs => obs.finally(() => subject.next()))
    .subscribe(val => console.log(Scheduler.async.now() - start, val));

我模拟了一个发出Observables的源Observable。外部Observable的发射速度比内部Observables的发射速度快,所以这应该模拟你的情况。

然后我将source合并到链中,但仅在subject发出时。然后在finally运算符中触发主题。

输出如下:

Subscribe inner Observable: 0
45 '0:0'
452 '0:1'
856 '0:2'
1260 '0:3'
1665 '0:4'
2065 '0:5'
Subscribe inner Observable: 2
2069 '2:0'
2472 '2:1'
2876 '2:2'
3280 '2:3'
3683 '2:4'
4084 '2:5'
Subscribe inner Observable: 4
4086 '4:0'
4487 '4:1'

请注意,当第一个内部Observable完成源的最新发射时,是一个带有i === 2的Observable。如果你运行这个代码,你会发现这三个排放之间没有时间差距(jsbin现在被打破,所以我无法登录并进行演示):

2065 '0:5'
Subscribe inner Observable: 2
2069 '2:0'

如果将此值与没有merge()的默认行为进行比较,您会发现exhaustMap需要等待来自源的另一次发射:

source
    .exhaustMap(obs => obs.finally(() => subject.next()))
    .subscribe(console.log);

这将打印以下内容。注意时间差距以及它使用i === 3而不是2订阅内部Observable:

Subscribe inner Observable: 0
45 '0:0'
449 '0:1'
853 '0:2'
1257 '0:3'
1659 '0:4'
2064 '0:5'
Subscribe inner Observable: 3
2748 '3:0'
3151 '3:1'
3553 '3:2'
3953 '3:3'
4355 '3:4'
4759 '3:5'
Subscribe inner Observable: 6
5458 '6:0'
5863 '6:1'

修改

为了避免两次订阅相同的内部Observable(假设这些是冷Observable),我可以跟踪我已经订阅的Observable指数以及下一步需要的索引:

我会让源以随机间隔和更少的值发光:

const source = Observable.range(0, 100, Scheduler.async)
    .concatMap(i => Observable.of(i).delay(Math.random() * 3000))
    .map(i => Observable.defer(() => {
        console.log('Subscribe inner Observable:', i);
        return Observable
            .timer(0, 400)
            .take(4)
            .map(j => i.toString() + ':' + j.toString());
    }))
    .map((observable, index) => [observable, index])
    .share();

然后使用subject.next()发送我们处理的索引,并忽略我们不想要的Observable:

source
    .merge(subject
        .withLatestFrom(source)
        .map(([processedIndex, observableAndIndex]) => {
            let observableIndex = observableAndIndex[1];
            if (processedIndex < observableIndex) {
                return observableAndIndex;
            }
            return false;
        })
        .filter(Boolean)
    )
    .exhaustMap(([observable, index]) => observable.finally(() => subject.next(index)))
    .subscribe(val => console.log(Scheduler.async.now() - start, val));

输出非常相似,但即使之前的Observable很快完成,我们也不会再次订阅它(例如,Observables 1和{{1}之间的时间间隔}):

2