BaconJS(FRP)的复杂操作

Complex operation with BaconJS (FRP)

我正在尝试在 BaconJs 中做这个相对复杂的操作。

基本上,这个想法是不断尝试每个 check,直到你有一个 'pass' 状态,或者他们都失败了。要注意的是 'pending' 状态有一个 Observables 列表(从 jquery ajax 请求构建)将解决检查。出于性能原因,您需要按顺序尝试每个 Observable,直到它们都通过或一个失败。

这是完整的伪算法:

这是培根代码。当 Observables 是 Ajax 请求时它不起作用。 基本上,它会跳过挂起的检查....它不会等待 ajax 对 return 的调用。如果我在 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();

更新:当检查如下所示时代码有效:[失败,失败,待定]。但是当检查看起来像这样时它不起作用:[失败,待定,通过]

我对 RxJS 比 Bacon 更熟悉,但我会说您没有看到所需行为的原因是 flatMap 不等人。

接连通过[fail, pending, pass]fail returns null被过滤掉。 pending 启动一个 observable,然后接收 pass 立即 returns check.id (培根可能不同,但在 RxJS 中 flatMap 不会接受单个值 return). 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. 如果 pendingcheck.observables 中创建一个扁平化的可观察对象,如果它们都是真的那么我们就完成了,否则继续下一个
  5. 使用 first 的谓词值得到第一个值 returned 即 done
  6. [可选] 去掉我们关心的值。

我同意@paulpdaniels 基于 Rx 的回答。问题似乎是,当使用 flatMap 时,Bacon.js 不会等待您的第一个 "check-stream" 完成后再启动一个新的。只需将 flatMap 替换为 flatMapConcat.

感谢@raimohanska 和@paulpdaniels。答案是使用#flatMapConcat。这基本上将并行完成的异步调用列表变成了按顺序完成的调用序列(请注意,最后一个 "check" 被编程为始终通过,因此它总是输出一些东西):

   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');