与BaconJS(FRP)的复杂操作

时间:2015-06-26 05:36:21

标签: functional-programming frp rxjs bacon.js

我正试图在BaconJs中进行相对复杂的操作。

基本上,我们的想法是继续尝试每个check,直到您获得“通过”状态或者都失败。问题是“待定”状态有一个Observable列表(由jquery ajax请求构建)将解决检查。出于性能原因,您需要按顺序尝试每个Observable,直到它们全部通过或者一个失败。

这是完整的伪算法:

  • 通过每张支票。 check包含idstatus =失败/通过/待定。如果待处理,则其中包含observables列表。
    • 如果status = pass,则返回id(你已完成!)
    • 如果status =失败,请尝试下一次检查
    • 如果status = pending
      • 按顺序尝试每个可观察对象
        • 如果可观察结果为“false”,则尝试下一次检查
      • 如果到达可观察列表的结尾并且结果为'true',则返回id(您已完成!)

这是培根代码。当Observable是Ajax请求时,它不起作用。 基本上,会发生的事情是它跳过挂起的检查....它不会等待ajax调用返回。如果我在filter()之前放置一个log(),它就不会记录挂起的请求:

    Bacon.fromArray(checks)
      .flatMap(function(check) {

        return check.status === 'pass' ? check.id :
          check.status === 'fail' ? null :
            Bacon.fromArray(check.observables)
              .flatMap(function(obs) { return obs; })
              .takeWhile(function(obsResult) { return obsResult; })
              .last()
              .map(function(obsResult) { return obsResult ? check.id : null; });
      })
      .filter(function(contextId) { return contextId !== null; })
      .first();

更新:当检查如下所示时,代码有效:[fail,fail,pending]。但是当检查看起来像这样时它不起作用:[fail,pending,pass]

3 个答案:

答案 0 :(得分:3)

我对RxJS比培根更熟悉,但我会说你没有看到所期望的行为是因为flatMap不等人。

它快速连续传递[fail, pending, pass]fail返回null并被过滤掉。 pending开始观察,然后接收pass立即返回check.id(培根可能不同,但在RxJS中,flatMap不会接受单个值返回)。 check.id会经过filter并点击first,此时它就会完成,它只会取消对ajax请求的订阅。

快速修复可能是使用concatMap而不是flatMap

在RxJS中虽然我会重构(免责声明未经测试):

Rx.Observable.fromArray(checks)
  //Process each check in order
  .concatMap(function(check) {
     var sources = {
       //If we pass then we are done
       'pass' : Rx.Observable.just({id : check.id, done : true}),
       //If we fail keep trying
       'fail' : Rx.Observable.just({done : false}),

       'pending' : Rx.Observable.defer(function(){ return check.observables;})
                                .concatAll()
                                .every()
                                .map(function(x) { 
                                  return x ? {done : true, id : check.id} : 
                                             {done : false};
                                })
     };

     return Rx.Observable.case(function() { return check.status; }, sources);
  })
  //Take the first value that is done
  .first(function(x) { return x.done; })
  .pluck('id');

以上是做什么的:

  1. 连接所有检查
  2. 使用case运算符进行传播,而不是嵌套的三元组。
  3. 失败传递快速
  4. 如果待处理check.observables创建一个展平的可观察对象,如果它们都是真的那么我们就完成了,否则继续下一个
  5. 使用first的谓词值来获取已完成的第一个返回值
  6. [可选]删除我们关心的值。

答案 1 :(得分:2)

我同意@paulpdaniels基于Rx的回答。问题似乎是当使用flatMap时,Bacon.js不会等到你的第一个“检查流”完成后再启动一个新的。只需将flatMap替换为flatMapConcat

答案 2 :(得分:1)

感谢@raimohanska和@paulpdaniels。答案是使用#flatMapConcat。这将基本上将并行执行的异步调用列表转换为按顺序执行的一系列调用(并注意最后一次"检查"被编程为始终通过以便始终输出内容):

   Bacon.fromArray(checks)
      .flatMapConcat(function(check) {

        var result = check();

        switch(result.status) {
          case 'pass' :
          case 'fail' :
            return result;
          case 'pending' :
            return Bacon.fromArray(result.observables)
              .flatMapConcat(function(obs) { return obs; })
              .takeWhile(function(obsResult) { return obsResult.result; })
              .last()
              .map(function (obsResult) { return obsResult ? {id: result.id, status: 'pass'} : {status: 'fail'}; });

        }
      })
      .filter(function(result) { return result.status === 'pass'; })
      .first()
      .map('.id');