按可观察对象过滤可观察对象
Filter an observable by an observable
让我们考虑以下简化情况:
- 我们有一个类型为 Observable < Apple > 的 Observable apples
- 每个 Apple 对象都有一个方法 isRotten(),它 returns 一个 Observable < Boolean > 类型的可观察对象,它保证至少发出一个布尔值。
我想过滤可观察到的苹果,这样烂苹果就不会通过过滤器。更准确地说,当且仅当 A.isRotten() 发出的第一项为假时,苹果 A 通过过滤器。实施此过滤器的最佳方式是什么?
经过一番思考,我想到了这个:
apples
.concatMap(apple =>
apple.isRotten()
.first()
.filter(bool => bool)
.map(bool => apple))
里面写的是javascript。 ( ... => ... 是一个函数)。这行得通,但我认为它相当冗长且难以理解。有没有更好的方法来做这种事情?
你可以这样做:
var seq = Rx.Observable.from([1, 2, 3, 4, 5, 6])
.filter(x => {
let isRotten = true;
Rx.Observable.just(x % 2 === 0)
.first()
.subscribe(val => isRotten = val);
if (isRotten) {
return x;
}
});
seq.subscribe(x => console.log(x));
你得到的很好,老实说,我想不出更简洁的方法了。如果乱序的苹果不是问题,我可能会使用 flatMap
而不是 concatMap
。
如果可读性对您来说是个问题,只需将实现移到它的一个函数中(例如,filterObservable
接受一个函数,该函数接受一个值和 returns 一个 IObservable<bool>
)
一种实现方式是这样的,抱歉我没能将它应用到水果过滤中:
const orders$: Observable<Order[]> = searchOrders(...);
const filteredOrders$ = orders$.pipe(switchMap((orders) => {
// take all orders and determine visibility based on an observable
const visibilityRules = orders.map(o => {
return {
order: o,
visible$: o.isPaidFor$ // this is an observable on an Order object
};
});
const filtered = visibilityRules.map(o => o.visible$.pipe(map(visible => visible ? o.order : undefined )));
return (filtered.length == 0) ? of([]) : combineLatest(filtered).pipe(map(combined => combined.filter(x => x != undefined)));
}));
这会过滤 'paidFor' 订单并在每次订单已支付或未支付时发出一个新数组。
注意:如果 isPaidFor$
observable 不能在搜索之间改变,那么整个练习就毫无意义,因为没有理由提供这样的 'live view'。这只有在可观察值在搜索结果之间实际发生变化时才有意义。
如果需要,这可以扩展到更复杂的规则(例如添加过滤复选框)——这就是我创建中间 visibilityRules
数组的原因——严格来说,这只是为了提高可读性。
让我们考虑以下简化情况:
- 我们有一个类型为 Observable < Apple > 的 Observable apples
- 每个 Apple 对象都有一个方法 isRotten(),它 returns 一个 Observable < Boolean > 类型的可观察对象,它保证至少发出一个布尔值。
我想过滤可观察到的苹果,这样烂苹果就不会通过过滤器。更准确地说,当且仅当 A.isRotten() 发出的第一项为假时,苹果 A 通过过滤器。实施此过滤器的最佳方式是什么?
经过一番思考,我想到了这个:
apples
.concatMap(apple =>
apple.isRotten()
.first()
.filter(bool => bool)
.map(bool => apple))
里面写的是javascript。 ( ... => ... 是一个函数)。这行得通,但我认为它相当冗长且难以理解。有没有更好的方法来做这种事情?
你可以这样做:
var seq = Rx.Observable.from([1, 2, 3, 4, 5, 6])
.filter(x => {
let isRotten = true;
Rx.Observable.just(x % 2 === 0)
.first()
.subscribe(val => isRotten = val);
if (isRotten) {
return x;
}
});
seq.subscribe(x => console.log(x));
你得到的很好,老实说,我想不出更简洁的方法了。如果乱序的苹果不是问题,我可能会使用 flatMap
而不是 concatMap
。
如果可读性对您来说是个问题,只需将实现移到它的一个函数中(例如,filterObservable
接受一个函数,该函数接受一个值和 returns 一个 IObservable<bool>
)
一种实现方式是这样的,抱歉我没能将它应用到水果过滤中:
const orders$: Observable<Order[]> = searchOrders(...);
const filteredOrders$ = orders$.pipe(switchMap((orders) => {
// take all orders and determine visibility based on an observable
const visibilityRules = orders.map(o => {
return {
order: o,
visible$: o.isPaidFor$ // this is an observable on an Order object
};
});
const filtered = visibilityRules.map(o => o.visible$.pipe(map(visible => visible ? o.order : undefined )));
return (filtered.length == 0) ? of([]) : combineLatest(filtered).pipe(map(combined => combined.filter(x => x != undefined)));
}));
这会过滤 'paidFor' 订单并在每次订单已支付或未支付时发出一个新数组。
注意:如果 isPaidFor$
observable 不能在搜索之间改变,那么整个练习就毫无意义,因为没有理由提供这样的 'live view'。这只有在可观察值在搜索结果之间实际发生变化时才有意义。
如果需要,这可以扩展到更复杂的规则(例如添加过滤复选框)——这就是我创建中间 visibilityRules
数组的原因——严格来说,这只是为了提高可读性。