【问题标题】:Context in Highland.jsHighland.js 中的上下文
【发布时间】:2015-03-17 19:50:10
【问题描述】:

我喜欢 Highland.js 和一般的响应式编程风格。我正在为失去上下文而苦苦挣扎,我正在尝试确定如何在目标是放弃状态的模型中优雅地处理上下文。

例如,我在 Amazon Web Services 中有一组帐户。

var accounts = [{accessId:"12345","secretKey":"abc123","account":"foo"},
                {accessId:"34512","secretKey":"def456","account":"bar"}];

我的目标基本上是为我在一个区域中运行的所有 EC2 实例创建一个电子表格。像这样。

Account   | Instance Size
--------- | -------------
foo       | m3.xlarge
foo       | c3.medium
bar       | t2.small

一般的工作流程是

  1. 浏览每个帐户
  2. 调用 ec2DescribeInstaces
  3. 以某种方式将每个 ec2DescribeInstances 回调映射到最终输出的帐户名称

在普通的 JavaScript 中,我们会在这里循环,所以当我们每次调用 ec2DescribeInstances 时,都会存在一个上下文

for ( account in accounts ) {
  var instances = ec2DescribeInstaces(account);
  for ( instance in instances ) {
    results.push({account:account.name, instanceSize: instance.size});
  }
}

根据我对反应式编程的理解,我会做这样的事情

_(accounts)
 .map(ec2DescribeInstaces)
 .parallel(2)
 .each(function(result) {
   results.push(result);
 });

任何指导??? 所以在这条链的末端,我有来自亚马逊的实例。但我不确定如何将这些与帐户联系起来以获取名称。我知道我可以绕过这个来获得价值,但我正在寻找最佳实践和优雅的东西。

所以@Bergi,如下所示???这将返回一个“上下文”对象,其中包含我需要的数据以及从亚马逊返回的数据。我对此唯一担心的是,如果我在整个链中传递上下文,那么我们将为调用进行大量数据提取、填充和包装。

ec2DescribeInstances = _.wrapCallback(function(accountData, callback) {
  // we remove the extraneous account name using a pick
  var ec2 = new AWS.EC2(lodash.pick(accountData,'accessKeyId','secretAccessKey'));
  ec2.describeInstances({ Filters: [{Name:'instance-state-name', Values:['running']}] }, function(err,data) {
    if ( err ) return callback(err);
    // here I create an object that wraps the AWS response
    callback(null, {"account":accountData.alias, "data": data})
  });
});

【问题讨论】:

  • 使用函数表达式来处理 ec2DescribeInstances 周围的帐户特定的事情
  • 好的,我在上面附加了一些内容以回应您的评论。这是推荐的方法吗?
  • 是的,我认为这是最简单的解决方案。

标签: javascript node.js highland.js


【解决方案1】:

您可以使用_.zip(),它接受两个流并返回一个对流。因此,以生成 ec2Instances 的流为例:

var ec2InstanceStream = _(accounts)
    .map(ec2DescribeInstaces)
    .parallel(2);

_(acounts).zip(ec2InstanceStream);
    .each(function(result) {
        //Each result: [{ /* original account */ }, { /* ec2 instance */ }]
        results.push(result);
    });

所以现在你把所有东西都放在一起了。你当然可以做得更好。例如,如果您想将每一对合并为一个对象,您可以在 .each() 步骤之前添加一个.map(_.extend) 以将对象合并在一起。

【讨论】:

  • 哇。那是正义的。我喜欢它,因为这样你就不必到处包装和打开所有现有的库了。
  • 是的,我发现对这类问题采用函数式方法相当让自己上瘾 :)。如果你真的很喜欢这种东西,我建议你在某个时候使用一种完全函数式的语言(例如 Haskell)。用这种完全不同的工作方式来思考真是太棒了:)。
【解决方案2】:

我会嵌套操作以获得一些范围:

const h = require("highland");
const ec2DescribeInstaces = h.wrapCallback(function(account, cb) {
  return cb(null, [{
    size: account.size
  }, {
    size: ++account.size
  }]);
});

const accounts = [{
  size: 1, name: 1
}, {
  size: 2, name: 2
}, {
  size: 3, name: 3
}];

var results = [];
h(accounts)
  .map(account => {
    return ec2DescribeInstaces(account)
      .flatten()
      .map(instance => ({
        account: account.name,
        instanceSize: instance.size,
      }));
  })
  .parallel(2)
  .tap(results.push.bind(results))
  .collect()
  .toCallback(h.log)

这样,当您使用该实例时,您仍然知道每个 instance 都与之相关的单个 account,并且流仍将并行运行,因为 map 的结果是一个流。

此外,我认为这是一种广泛适用的方法。无论您返回多少实例或您想为每个帐户做多少事情都无关紧要。例如,如果您需要为帐户上的每个实例将两个值推送到 results,您可以从 map 返回和 parallel(2) 两个流,并且您不需要完全重新调整您的方法,只需调整行为。

【讨论】:

    猜你喜欢
    • 2015-02-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多