BaconJS(FRP)的复杂操作
Complex operation with BaconJS (FRP)
我正在尝试在 BaconJs 中做这个相对复杂的操作。
基本上,这个想法是不断尝试每个 check
,直到你有一个 'pass' 状态,或者他们都失败了。要注意的是 'pending' 状态有一个 Observables 列表(从 jquery ajax 请求构建)将解决检查。出于性能原因,您需要按顺序尝试每个 Observable,直到它们都通过或一个失败。
这是完整的伪算法:
- 通过每一项检查。
check
包含 id
和 status
= fail/pass/pending。如果待定,则它包含 observables
的列表。
- 如果 status = pass,则 return ID(大功告成!)
- 如果状态 = 失败,则尝试下一个检查
- 如果状态 = 待定
- 按顺序尝试每个可观察对象
- 如果可观察结果是'false',则尝试下一个检查
- 如果到达可观察列表的末尾且结果为 'true',则 return id(完成!)
这是培根代码。当 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');
上面所做的是:
- 连接所有支票
- 使用
case
运算符来传播而不是嵌套的三元组。
- 失败或通过快速
- 如果 pending 从
check.observables
中创建一个扁平化的可观察对象,如果它们都是真的那么我们就完成了,否则继续下一个
- 使用
first
的谓词值得到第一个值 returned 即 done
- [可选] 去掉我们关心的值。
我同意@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');
我正在尝试在 BaconJs 中做这个相对复杂的操作。
基本上,这个想法是不断尝试每个 check
,直到你有一个 'pass' 状态,或者他们都失败了。要注意的是 'pending' 状态有一个 Observables 列表(从 jquery ajax 请求构建)将解决检查。出于性能原因,您需要按顺序尝试每个 Observable,直到它们都通过或一个失败。
这是完整的伪算法:
- 通过每一项检查。
check
包含id
和status
= fail/pass/pending。如果待定,则它包含observables
的列表。- 如果 status = pass,则 return ID(大功告成!)
- 如果状态 = 失败,则尝试下一个检查
- 如果状态 = 待定
- 按顺序尝试每个可观察对象
- 如果可观察结果是'false',则尝试下一个检查
- 如果到达可观察列表的末尾且结果为 'true',则 return id(完成!)
- 按顺序尝试每个可观察对象
这是培根代码。当 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');
上面所做的是:
- 连接所有支票
- 使用
case
运算符来传播而不是嵌套的三元组。 - 失败或通过快速
- 如果 pending 从
check.observables
中创建一个扁平化的可观察对象,如果它们都是真的那么我们就完成了,否则继续下一个 - 使用
first
的谓词值得到第一个值 returned 即 done - [可选] 去掉我们关心的值。
我同意@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');