如何基于密钥加入流

时间:2018-04-27 20:34:19

标签: rxjs redux-observable

这适用于redux-observable,但我认为一般模式对于rxjs非常通用

我有一系列事件(来自redux-observable,这些是redux操作),我特别想要为同一个"资源"配对两种不同类型的事件。 - "资源集活跃"和#34;资源加载" - 当这些事件与#34;匹配时,发出新事件。问题是这些可以以任何顺序进入任何资源,并且可以多次触发。您可以在加载之前设置一些活动的东西,或者在设置为活动之前加载某些东西,并且可以在其间加载或设置其他资源。

我想要的是一个"这个资源现已加载,现在已经处于活动状态" - 这也意味着一旦加载了资源,就可以认为它永远被加载了。

如果这些事件没有被资源ID键入,那么它将非常简单:

首先我会按类型拆分它们:

const setActive$ = action$.filter(a => a.type == 'set_active');
const load = action$.filter(a => a.type == 'loaded');

在没有键控的简单情况下,我会说:

const readyevent$ = setActive$.withLatestFrom(loaded$)

然后readyevent$只是set_active个事件的流,其中至少有一个loaded事件。

但我的问题是set_activeloaded事件都是由资源ID键入的,并且由于我无法控制的原因,在两个事件中标识资源的属性是不同的。所以这就像:

const setActive$ = action$.filter(a => a.type === 'set_active').groupBy(a => a.activeResourceId);
const loaded$ = action$.filter(a => a.type === 'loaded').groupBy(a => a.id);

但从这一点来说,我无法确定如何重新加入 "这两个分组可观察流在同一个密钥上,这样我就可以发出withLatestFrom个动作流。

2 个答案:

答案 0 :(得分:1)

我相信这就是你所描述的:



const action$ = Rx.Observable.from([
  { activeResourceId: 1, type: 'set_active' },
  { id: 2, type: 'loaded' },
  { id: 1, type: 'random' },
  { id: 1, type: 'loaded' },
  { activeResourceId: 2, type: 'set_active' }
]).zip(
  Rx.Observable.interval(500),
  (x) => x
).do((x) => { console.log('action', x); }).share();

const setActiveAction$ = action$.filter(a => a.type === 'set_active')
  .map(a => a.activeResourceId)
  .distinct();
const allActive$ = setActiveAction$.scan((acc, curr) => [...acc, curr], []);
  
const loadedAction$ = action$.filter(a => a.type === 'loaded')
  .map(a => a.id)
  .distinct();
const allLoaded$ = loadedAction$.scan((acc, curr) => [...acc, curr], []);
  
Rx.Observable.merge(
  setActiveAction$
    .withLatestFrom(allLoaded$)
    .filter(([activeId, loaded]) => loaded.includes(activeId)),
  loadedAction$
    .withLatestFrom(allActive$)
    .filter(([loadedId, active]) => active.includes(loadedId))
).map(([id]) => id)
 .distinct()
 .subscribe((id) => { console.log(`${id} is loaded and active`); });

<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.10/Rx.min.js"></script>
&#13;
&#13;
&#13;

基本方法是为每个操作类型创建一个不同的流,并将其与另一个的不同聚合连接起来。然后合并两个流。当匹配的setActive和加载事件时,这将发出值。合并结束时的distinct()会使您只收到一次通知。如果您希望在初始操作之后对每个setActive操作发出通知,则只需删除该操作符。

答案 1 :(得分:0)

groupBy执行此操作看起来有点复杂,那里有一个关键值,但你得到一个Observable of Observables - 所以可能有点难以做对。

我会将id映射到公共属性,然后使用扫描进行组合。我在我的应用程序中使用此模式进行分组。

扫描中的累加器是一个对象,用作关联数组 - 每个属性都是一个id,属性值是到目前为止累积的动作数组。

在扫描之后,我们转换为一个可观察的匹配操作数组流 - 有点像withLatestFrom但有些数组可能尚未完成。

下一步是筛选我们认为完整的阵列 既然你说

  

至少有一个已加载的事件

我将假设存在两个或多个动作,其中至少有一个动作已加载&#39; - 但从你的问题中判断这是否足够,这有点棘手。

最后,重置累加器中的id,因为它可能会在稍后的流中再次出现。

const setActive$ = action$.filter(a => a.type === 'set_active')
  .map(a => { return { id: a.activeResourceId, action: a } });

const loaded$ = action$.filter(a => a.type === 'loaded')
  .map(a => { return { id: a.id, action: a } });

const accumulator = {};
const readyevent$: Observable<action[]> = 
  Observable.merge(setActive$, loaded$)
  .scan((acc, curr) => {
    acc[curr.id] = acc[curr.id] || [];
    acc[curr.id].push(curr.action)
  }, accumulator)
  .mergeMap((grouped: {}) => Observable.from(
    Object.keys(grouped).map(key => grouped[key])
  ))
  .filter((actions: action[]) => {
    return actions.length > 1 && actions.some(a => a.type === 'loaded')
   })
  .do(actions => {
    const id = actions.find(a => a.type === 'loaded').id;
    accumulator[id] = [];
  });