执行连接后,为什么不调用complete?

时间:2016-11-08 15:57:16

标签: javascript rxjs rxjs5 reactivex redux-observable

我需要多次查询设备。每个查询都需要是异步的,并且设备一次不支持同时查询。 而且,一旦查询,就不能立即再次查询。它需要至少1秒的暂停才能正常工作。

我的两个查询由saveClock()saveConfig()执行,返回一个Promise,并通过按预期返回undefined来解析。

在以下代码中,为什么删除take()会阻止调用toArray()
这里发生了什么,是否有更好的方法来实现相同的行为?

export const saveEpic = (action$, store) =>
  action$.ofType(SAVE)
    .map(action => {
      // access store and create object data
      // ...
      return data;
    })
    .mergeMap(data =>
      Rx.Observable.from([
        Rx.Observable.of(data).mergeMap(data => saveClock(data.id, data.clock)),
        Rx.Observable.timer(1000),
        Rx.Observable.of(data).mergeMap(data => saveConfig(data.id, data.config)),
        Rx.Observable.of(data.id)
     ])
    )
    .concatAll()
    .take(4)
    .toArray()
    // [undefined, 0, undefined, "id"]
    .map(x => { type: COMPLETED, id: x[3] });

1 个答案:

答案 0 :(得分:2)

我看到了几件事:

您的最终.map()缺少括号,其当前形式是语法错误,但微妙的更改可能会使其意外地成为labeled statement而不是返回对象。因为它目前的形式是一个语法错误,我想这只是这篇文章中的一个错误,而不是你的代码(它甚至不会运行),而是仔细检查!

// before
.map(x => { type: COMPLETED, id: x[3] });

// after
.map(x => ({ type: COMPLETED, id: x[3] }));

修复后,该示例确实运行了一个简单的redux-observable测试用例:http://jsbin.com/hunale/edit?js,output所以,如果没有什么值得注意的,我做的与你不同,问题出现到在未提供的代码中。随意添加更多洞察力甚至更好,在我们的JSBin / git repo中重现它。

你没有提到的一件事,但非常值得注意的是,在redux-observable中,你的史诗通常是长寿的过程经理"。这个史诗实际上只会处理其中一个保存,然后是complete(),这可能不是你真正想要的?用户每个应用程序启动只能保存一次吗?似乎不太可能。

相反,您希望通过将此逻辑封装在mergeMap中来保持顶级流您的史诗返回活动并监听将来的操作。 take(4)并传递data.id然后变得无关:

const saveEpic = (action$, store) =>
  action$.ofType(SAVE)
    .mergeMap(data =>
      Rx.Observable.from([
        Rx.Observable.of(data).mergeMap(data => saveClock(data.id, data.clock)),
        Rx.Observable.timer(1000),
        Rx.Observable.of(data).mergeMap(data => saveConfig(data.id, data.config))
      ])
      .concatAll()
      .toArray()
      .map(() => ({ type: COMPLETED, id: data.id }))
    );

Ben Lesh在他最近的AngularConnect演讲中描述了这种流的分离,在错误的背景下,它仍然适用:https://youtu.be/3LKMwkuK0ZE?t=20m(别担心,这不是' t Angular specific!)

接下来,我想分享一些不请自来的重构建议,这些建议可能会让您的生活更轻松,但当然这是自以为是的,所以可以随意忽略:

我会重构以更直观地反映事件的顺序,并降低复杂性:

const saveEpic = (action$, store) =>
  action$.ofType(SAVE)
    .mergeMap(data =>
      Rx.Observable.from(saveClock(data.id, data.clock))
        .delay(1000)
        .mergeMap(() => saveConfig(data.id, data.config))
        .map(() => ({ type: COMPLETED, id: data.id }))
    );

我们在这里消耗saveClock返回的Promise,将其输出延迟1000ms,mergeMapping结果调用saveConfig()同时返回一个Promise被消耗然后最终将结果映射到我们的COMPLETE操作。

最后,请记住,如果你的Epic 确实 保持活力并且长寿,那么这个史诗中没有任何东西阻止它接收多个SAVE请求而其他的请求仍在飞行中或尚未用尽请求之间所需的1000毫秒延迟。即如果确实需要任何请求之间的1000毫秒空间,那么你的史诗本身并不能完全阻止你的UI代码破坏它。在这种情况下,您可能需要考虑添加更复杂的缓冲backpressure机制,例如使用带有.zip()的{​​{1}}运算符。

http://jsbin.com/waqipol/edit?js,output

BehaviorSubject

这使得在我们仍在处理现有请求时保存的请求被缓冲,并且我们一次只接受其中一个。请记住,这是一个无限制的缓冲区 - 这意味着待处理操作的队列可能比刷新缓冲区的速度无限快地增长。除非您采用有损背压策略,例如删除重叠请求等,否则这是不可避免的。

如果你有其他史诗有重叠的要求,不能每秒发送一次以上的请求,你需要创建某种单一的主管,为所有的史诗提供这种保证。

这可能看起来都非常复杂,但也许具有讽刺意味的是,在RxJS中,这比传统的命令式代码更容易实现 。最难的部分实际上是了解模式。