平整嵌套的callback

这是一个callback(稍微修改/简化为这个问题的目的)我已经写了处理一些对象数据。

function (err, data) { var rv; if (data && data.result instanceof Array) { rv = data.result.map(function (value) { if (value && value.track_contributions instanceof Array) { return value.track_contributions.map(function (innerValue) { if (innerValue && innerValue.track) { return innerValue.track.mid; } }); } }); // flatten nested arrays rv = [].concat.apply([], rv); } console.log(rv); }; 

以下是我正在处理的对象:

 { "result": [ { "type": "/music/artist", "track_contributions": [ { "track": { "mid": "/m/015rm3l" } }, { "track": { "mid": "/m/0nm2km" } }, { "track": { "mid": "/m/010ksbq" } }, ... ] } ] } 

我想从我的callback看起来像这样的返回值:

 [ '/m/015rm3l', '/m/0nm2km', '/m/010ksbq', ... ] 

我的代码工作正常,但我觉得所有的嵌套是一种代码味道。

我应该如何使这种types的代码更平坦,更可读和可维护,以及所有这些伟大的东西? 承诺? 一些lodashtypes的工具? 别的东西? 上述所有的?

下面是我认为更简单的版本,不需要重复的代码,因为它使用通用的遍历和callback来添加数据结构的细节:

 function process(err, data) { var rv = []; function doObject(obj, key, callback) { if (obj[key] && obj[key] instanceof Array) { obj[key].forEach(function(value) { callback(value); }); } } doObject(data, "result", function(result) { doObject(result, "track_contributions", function(item) { if (item.track && item.track.mid) { rv.push(item.track.mid); } }); }); console.log(rv); } 

在不改变你使用的基本algorithm的情况下,你可以初始化rv ,直接将结果直接推送到rv而不是使子arrays变得扁平化。

 function process(err, data) { var rv = []; if (data && data.result instanceof Array) { data.result.forEach(function(value) { if (value && value.track_contributions instanceof Array) { value.track_contributions.forEach(function(innerValue) { if (innerValue && innerValue.track) { rv.push(innerValue.track.mid); } }); } }); } console.log(rv); } 

因为您在每个级别上查找的属性名称是不同的,所以使用recursion来避免重复的代码(尽pipe这是一个选项)并不是那么高效或容易。

这是一个两级recursionalgorithm,但我不认为增加的复杂性实际上是值得的。 如果你要深入2层以上或者任意数量的层次,这可能会更有意义:

 function process(err, data) { var rv = []; function doArray(item, key1, key2) { if (item && item[key1] instanceofArray) { item[key1].forEach(function(value) { if (key2) { doArray(value, key2); } else if (value && value.track.mid) { rv.push(value.track.mid); } }); } } doArray(data, "result", "track_contributions"); console.log(rv); } 

这种方法将得到您想要的输出:

 function getArray(err, data) { var rv; function mapArray(obj, array, callback) { if (obj && obj[array] instanceof Array) { return obj[array].map(callback); } } function getValue(value) { return mapArray(value, 'track_contributions', getInnerValue); } function getInnerValue(innerValue) { return innerValue.track && innerValue.track.mid; } rv = [].concat.apply([], mapArray(data, 'result', getValue)); console.log(rv); }; getArray(null, data); 

关键是将每个function分解到其所需的目的,并重用每一个重复的function。

例如映射两次。 所以让我们写一个generics函数mapArray 。 它期望一个obj ,数组属性array的名称和callback。

对于你的例子中的外部函数,我们有mapArray(data, 'result', getValue) ,对于内部函数,它是mapArray(value, 'track_contributions', getInnerValue)

getValue函数内部,我们定义了外部函数, getInnerValues定义了内部函数。

完成。

您将数据结构的遍历与要应用于其节点的域逻辑进行混合,因此会产生代码异味。 最好解耦两个方面,有一个通用的遍历algorithm提供一个域function(在你现在的情况下collections家)。

例如:

  function traverse(source, collector) { for(var property in source) { var value = source[property]; if (value instanceof Array) { for(var ii=0; ii<value.length; ii++) { arguments.callee(value[ii], collector); } } else if (value instanceof Object){ arguments.callee(value, collector); } else { collector(property, value); } } } var values=[]; traverse(source, function(property, value) { if (property == "mid") { values.push(value); } }); document.write(values.join(", ")); 

遍历algorithm可能更复杂,这取决于约束原始数据结构的规则; 一定要保持最小(即不要写代码,你将不会使用)。