我在Javascript中实现了黑板模式,我的黑板控件遍历知识源/专家,并调用他们的execAction()。
for(let expert of this.blackboard.experts){
// Check execution condition
}
mostRelevantExpert.executeAction();
现在问题是,这些知识源经常需要调用远程API或读取文件,而且大多数库只提供回调API
class myExpert{
executeAction() {
myLibrary.call(params, (error, response) => { continueHere; })
}
}
当然这完全弄乱了我的黑板流。
我不确定解决方案是否会以“异步”方式重新实现整个黑板,或者是否有更聪明的方法。
我尝试使用像deasync这样的库,但问题是我实际上在myLibrary.call(params, (error, response) => { bueHere; }
中有一个错误,我现在还不太明白如何调试它。由于我将来可能会有更多这样的问题,我想知道应该采取什么行动。
使用节点6,ES6,我不喜欢使用回调编程风格来处理我在这里所做的事情。
node debug app.js
编辑:
这是我的Blackboard Control代码:
module.exports = class BlackboardControl{
constructor(blackboard){
this.blackboard = blackboard;
}
loop(){
console.log('¤ Blackboard Control');
console.log(' Starting Blackboard loop');
// Problem solved when there is a technicianAnswer, so the bot has something to say
while(!this.blackboard.problemSolved) {
// Select experts who can contribute to the problem
let candidates = [];
for(let expert of this.experts){
let eagerness = expert.canContribute();
if(eagerness){
candidates.push([eagerness,expert]);
}
}
if(candidates.length === 0) {
console.log('No expert can\'t do anything, returning');
return;
}
// Sort them by eagerness
candidates.sort(function(a,b) {
return a[0]-b[0];
});
for(let eagerExpert of candidates){
console.log('Next expert elected : ' + eagerExpert[1].constructor.name);
eagerExpert[1].execAction();
}
}
}
};
答案 0 :(得分:2)
我还没有尝试过,但是(很大程度上是因为我必须发明任意的问题空间,而且我觉得现在朝着另一个方向旅行会更容易)
但是如果你想看一下异步流的样子,我可能会考虑这样的事情:
async function getEngagedExperts (experts, problem) {
const getContributor = expert => expert.canContribute(problem)
.then(eagerness => [eagerness, expert]);
const contributors = await Promise.all(experts.map(getContributor));
return contributors.filter(([eager]) => eager);
}
async function contribute (previousState, expert) {
const state = await previousState;
return expert.execAction(state);
}
async function solveProblem (problem, experts) {
if (problem.solved) { return problem; }
const candidates = (await getEngagedExperts(experts, problem))
.sort(([a], [b]) => a - b)
.map(([, expert]) => expert);
const result = await candidates.reduce(contribute, Promise.resolve(problem));
return candidates.length ? solveProblem(result, experts) : undefined;
}
在ES6中工作,如果你有一个像co
这样的库来管理从迭代器返回的promise。
编写自己的co实现并不困难,但这完全不是它的空间。
const getEngagedExperts = co.wrap(function * getEngagedExperts (experts, problem) {
const getContributor = expert => expert.canContribute(problem)
.then(eagerness => [eagerness, expert]);
const contributors = yield Promise.all(experts.map(getContributor));
return contributors.filter(([eager]) => eager);
});
const contribute = co.wrap(function * contribute (previousState, expert) {
const state = yield previousState;
return expert.execAction(state);
});
const solveProblem = co.wrap(function * solveProblem (problem, experts) {
if (problem.solved) { return problem; }
const candidates = (yield getEngagedExperts(experts, problem)))
.sort(([a], [b]) => a - b)
.map(([, expert]) => expert);
const result = yield candidates.reduce(contribute, Promise.resolve(problem));
return candidates.length ? solveProblem(result, experts) : undefined;
});
当其他所有方法都失败时,请手工编写,并且好好地写下来。 ES5,再加上承诺。
function getEngagedExperts (experts, problem) {
function getContributor (expert) {
return expert.canContribute(problem).then(eagerness => [eagerness, expert]);
}
function filterContributors (contributors) {
return contributors.filter(function (pair) {
const eagerness = pair[0];
return eagerness;
});
}
const getContributors = Promise.all(experts.map(getContributor));
return getContributors.then(filterContributors);
}
function contribute (previousComputation, expert) {
return previousComputation.then(function (state) {
return expert.execAction(state);
});
}
function solveProblem (problem, experts) {
if (problem.solved) { return problem; }
const getCandidates = getEngagedExperts(experts, problem)
.then(function (candidates) {
return candidates
.sort(function (a, b) { return a[0] - b[0]; })
.map(function (pair) { return pair[1]; });
});
return getCandidates.then(function (candidates) {
const getResult = candidates.reduce(contribute, Promise.resolve(problem));
return getResult.then(function (result) {
return candidates.length ? solveProblem(result, experts) : undefined;
});
});
}
答案 1 :(得分:1)
这是基于我(不完整)理解您的问题的尝试。这些是我使用的前提:
Expert
个对象,这些对象提供通过executeAction()
方法执行某种工作的异步函数。BlackboardControl
对象汇集这些专家,并负责按顺序运行它们,直到其中一个返回成功结果。此对象还包含封装在blackboard
属性中的某种状态。基于承诺的解决方案的第一步是使executeAction()
方法返回一个promise,而不是要求回调。使用Bluebird提供的promisifyAll()
utility可以轻松更改整个节点样式库的调用约定:
// module MyExpert ---------------------------------------------------------
var Promise = require('bluebird');
// dummy library with a node-style async function, let's promisify it
var myLibrary = Promise.promisifyAll({
someFunc: function (params, callback) {
setTimeout(() => {
if (Math.random() < 0.4) callback('someFunc failed');
else callback(null, {inputParams: params});
}, Math.random() * 1000 + 100);
}
});
class MyExpert {
executeAction(params) {
return myLibrary.someFuncAsync(params); // returns a promise!
}
}
module.exports = MyExpert;
现在,我们需要一个BlackboardControl
对象做两件事:从池中取出下一个免费的Expert
对象(nextAvailableExpert()
)并通过应用专家来解决给定的问题按顺序,直到其中一个成功或达到最大重试次数(solve()
)。
// module BlackboardControl ------------------------------------------------
var Promise = require('bluebird');
var MyExpert = require('./MyExpert');
class BlackboardControl {
constructor(blackboard) {
this.blackboard = blackboard;
this.experts = [/* an array of experts */];
}
nextAvailableExpert() {
return new MyExpert();
// yours would look more like this
return this.experts
.map((x) => ({eagerness: x.canContribute(), expert: x}))
.filter((ex) => ex.eagerness > 0)
.sort((exA, exB) => exA.eagerness - exB.eagerness)
.map((ex) => ex.expert)
.pop();
}
solve(options) {
var self = this;
var expert = this.nextAvailableExpert();
if (!expert) {
return Promise.reject('no expert available');
} else {
console.info('Next expert elected : ' + expert.constructor.name);
}
options = options || {};
options.attempt = +options.attempt || 0;
options.maxAttempts = +options.maxAttempts || 10;
return expert.executeAction(/* call parameters here */).catch(error => {
options.attempt++;
console.error("failed to solve in attempt " + options.attempt + ": " + error);
if (options.attempt <= options.maxAttempts) return self.solve(options);
return Promise.reject("gave up after " + options.maxAttempts + " attempts.");
});
}
}
module.exports = BlackboardControl;
关键是这一行:
if (options.attempt <= options.maxAttempts) return self.solve(options);
承诺链。如果你从promise回调中返回一个新的promise(在这种情况下来自catch()
处理程序,因为我们想在专家失败时重新开始),promise的整体结果将由这个新promise的结果决定。换句话说,新的承诺将被执行。这是我们的迭代步骤。
这样从solve()
返回一个承诺就可以通过在错误处理程序中再次调用solve()
来启用内部重复 - 并且它允许通过then()
进行外部响应,如下面的示例用法所示: / p>
// usage -------------------------------------------------------------------
var BlackboardControl = require('./BlackboardControl');
var bbControl = new BlackboardControl({ /* blackboard object */ });
var result = bbControl.solve({
maxAttempts: 10
}).then(response => {
console.log("continueHere: ", response);
}).catch(reason => {
console.error(reason);
});
创建这样的输出(这里虚拟函数连续五次失败):
Next expert elected : MyExpert failed to solve in attempt 1: Error: someFunc failed Next expert elected : MyExpert failed to solve in attempt 2: Error: someFunc failed Next expert elected : MyExpert failed to solve in attempt 3: Error: someFunc failed Next expert elected : MyExpert failed to solve in attempt 4: Error: someFunc failed Next expert elected : MyExpert failed to solve in attempt 5: Error: someFunc failed Next expert elected : MyExpert continueHere: { some: 'parameters' }
在专家运行期间,控制权返回主程序。由于现在多个专家可以在多个问题上同时运行,我们无法预先列出可用的专家列表。每当我们需要专家时,我们必须做出新的决定,因此nextAvailableExpert()
功能。
答案 2 :(得分:-1)
是的,我确实设法使deasync代码工作。原来我试图使用
const deasync = require('deasync');
try {
const deasyncAnswer = deasync(Lib.foo(
myParam,
// Callback was here
);
}
但使用它的正确方法是
const fooDeasynced= deasync(Lib.foo);
try {
const deasyncAnswer = fooDeasynced(myparams)
}