黑板模式控制流程中的Javascript回调函数

时间:2016-06-19 14:25:22

标签: javascript node.js

我在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,我不喜欢使用回调编程风格来处理我在这里所做的事情。

  • 我应该如何在Javascript中使用黑板模式?
  • 如何使用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();

      }
    }
  }
};

3 个答案:

答案 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发电机+产量

在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 +承诺

当其他所有方法都失败时,请手工编写,并且好好地写下来。 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)
}