Highland.js 中的上下文
Context in Highland.js
我喜欢 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
一般的工作流程是
- 浏览每个帐户
- 调用 ec2DescribeInstaces
- 以某种方式将每个 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,就像下面这样???这将是必不可少的 return 一个 "context" 对象,其中包含我需要的数据以及从 Amazon 返回的数据。我对此唯一担心的是,如果我在整个链中传递上下文,那么我们将为调用进行大量数据采集、填充和包装。
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})
});
});
您可以使用 _.zip()
,它接受两个流,returns 接受一对流。因此,以生成 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)
以将对象合并在一起。
我会嵌套操作以获得一些范围:
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
,您可以 return 和 parallel(2)
来自 map
的两个流,而您不会需要完全重新调整您的方法,只需调整行为即可。
我喜欢 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
一般的工作流程是
- 浏览每个帐户
- 调用 ec2DescribeInstaces
- 以某种方式将每个 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,就像下面这样???这将是必不可少的 return 一个 "context" 对象,其中包含我需要的数据以及从 Amazon 返回的数据。我对此唯一担心的是,如果我在整个链中传递上下文,那么我们将为调用进行大量数据采集、填充和包装。
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})
});
});
您可以使用 _.zip()
,它接受两个流,returns 接受一对流。因此,以生成 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)
以将对象合并在一起。
我会嵌套操作以获得一些范围:
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
,您可以 return 和 parallel(2)
来自 map
的两个流,而您不会需要完全重新调整您的方法,只需调整行为即可。