`combineLatest`,`switchMap`和保留内部订阅

时间:2018-08-16 11:05:27

标签: rxjs observable

我有一个Observable<Array<Observable<T>>>,我想映射到Observable<Array<T>>

发射新数组时,内部可观察对象应按以下方式取消订阅/订阅:

  • 如果Observable存在于先前的数组和新的/当前的数组中,则保留预先存在的订阅
  • 如果Observable在先前的数组中不存在,但在新的/当前的数组中存在,则创建新的订阅
  • 如果Observable在先前的数组中存在,但在新的/当前的数组中不存在,请从预先存在的订阅中取消订阅

我希望在外部可观察的物体上使用switchMap,然后将Array<Observable<T>>传递到combineLatest中来实现此目的。但是,switchMap将在订阅新的内部Observable之前从其先前的内部Observable退订,这意味着内部订阅不会按需保留。

示例(https://stackblitz.com/edit/typescript-b4wgr1)。给定代码:

import 'rxjs/Rx';
import { Observable } from 'rxjs';

const debugObservable = <T>(t$: Observable<T>, name: string) =>
    new Observable<T>(observer => {
        console.log(name, 'subscribe');
        const subscription = t$.subscribe(observer);
        return () => {
            console.log(name, 'unsubscribe');
            return subscription.unsubscribe();
        };
    });

const ofSingle = <T>(t: T) =>
    new Observable<T>(observer => {
        observer.next(t);
    });

const observableOfArrayOfObservablesOfNumber = new Observable<
    Array<Observable<number>>
>(observe => {
    const keep = debugObservable(ofSingle(1), 'keep');
    const remove = debugObservable(ofSingle(2), 'remove');
    const add = debugObservable(ofSingle(3), 'add');

    observe.next([keep, remove]);

    setTimeout(() => {
        observe.next([keep, add]);
    }, 2000);

    return () => {};
});

// The `switchMap` will unsubscribe to the previous inner observable *before* subscribing to the new
// inner observable.
const final$ = observableOfArrayOfObservablesOfNumber.switchMap(
    arrayOfObservablesOfNumber => {
        const observableOfArrayOfNumbers = Observable.combineLatest(
            arrayOfObservablesOfNumber,
        );
        return debugObservable(
            observableOfArrayOfNumbers,
            'observableOfArrayOfNumbers',
        );
    },
);

final$.subscribe(x => console.log('final', x));

这将产生:

observableOfArrayOfNumbers subscribe
keep subscribe
remove subscribe
final [1, 2]
keep unsubscribe <--- bad!
remove unsubscribe
observableOfArrayOfNumbers unsubscribe
observableOfArrayOfNumbers subscribe
keep subscribe <--- bad!
add subscribe
final [1, 3]

但是,这就是我想要的:

observableOfArrayOfNumbers subscribe
keep subscribe
remove subscribe
final [1, 2]
remove unsubscribe
observableOfArrayOfNumbers unsubscribe
observableOfArrayOfNumbers subscribe
add subscribe
final [1, 3]

3 个答案:

答案 0 :(得分:0)

我最终通过使用publishReplay(1)发布+重播内部可观察对象,然后进行引用计数来实现这一目标。

请注意,refCount是不够的,因为当0取消订阅先前的内部可观察对象(在订阅新的内部可观察对象之前)时,计数将下降到switchMap。使用特殊的refCountWithDelay运算符,该运算符仅在延迟后(即,在事件循环的同一滴答声中但不同步)通过引用计数来取消订阅。此处的更多信息:

https://stackblitz.com/edit/typescript-4xfwsh?file=index.ts

const createObservable = <T>(t: T, name: string) => {
  return refCountWithDelay(debugObservable(ofSingle(t), name).publishReplay(1), 0, 0);
}

const observableOfArrayOfObservablesOfNumber = new Observable<
    Array<Observable<number>>
>(observe => {
    const keep = createObservable(1, 'keep');
    const remove = createObservable(2, 'remove');
    const add = createObservable(3, 'add');

    observe.next([keep, remove]);

    setTimeout(() => {
        observe.next([keep, add]);
    }, 2000);

    return () => {};
});

产生:

observableOfArrayOfNumbers subscribe
keep subscribe
remove subscribe
final [1, 2]
observableOfArrayOfNumbers unsubscribe
observableOfArrayOfNumbers subscribe
remove unsubscribe
add subscribe
final [1, 3]

请注意,keep只订阅了一次。

答案 1 :(得分:0)

我想出了一个更好的解决方案,可以使用None is (None == None) (None is None) == None 中的combineLatestHigherOrderhttps://github.com/cartant/rxjs-etc

https://stackblitz.com/edit/typescript-hfze6m?file=index.ts

答案 2 :(得分:-1)

与您所描述的最接近的是在Cycle.js Onionify中称为pickCombine的xstream运算符。

似乎没有一个单一的官方RxJS运算符可以解决此问题,但是可以构建自己的运算符来实现此行为。您可以使用pickCombine的xstream实现作为参考。

关键部分是:

请注意,创建custom data structure(使用Map并依靠键来消除数组项的歧义)比直接在数组上进行创建要容易和高效。您可以从外部API隐藏自定义数据结构。