带着承诺走一棵树
Posted
技术标签:
【中文标题】带着承诺走一棵树【英文标题】:walking a tree with Promises 【发布时间】:2016-04-14 13:09:15 【问题描述】:我有一个要使用 Promises 遍历的树形结构,但我还没有找到正确的代码模式。
假设我们得到了节点名称,并且将节点名称转换为节点的行为是一个异步过程(例如,涉及 Web 访问)。还假设每个节点都包含一个(可能为空的)子名称列表:
function getNodeAsync(node_name)
// returns a Promise that produces a node
function childrenNamesOf(node)
// returns a list of child node names
我想用这个签名的方法结束:
function walkTree(root_name, visit_fn)
// call visit_fn(root_node), then call walkTree() on each of the
// childrenNamesOf(root_node), returning a Promise that is fulfilled
// after the root_node and all of its children have been visited.
它返回一个在根节点及其所有子节点都被访问后实现的 Promise,因此它可能被如下调用:
walkTree("grandpa", function(node) console.log("visiting " + node.name); )
.then(function(nodes) console.log("found " + nodes.length + " nodes."));
更新
I've create a gist that shows my first attempt。我对 walkTree() 的(有点错误的)实现是:
function walkTree(node_name, visit_fn)
return getNodeAsync(node_name)
.then(function(node)
visit_fn(node);
var child_names = childrenNamesOf(node);
var promises = child_names.map(function(child_name)
walkTree(child_name, visit_fn);
);
return Promise.all(promises);
);
;
这会以正确的顺序访问节点,但最外层的 Promise 在所有子节点都被访问之前解析。 See the gist for full details.
正如@MinusFour 指出的那样,使用这种技术来展平节点列表是毫无意义的。事实上,我真的只想在所有节点都被访问后触发最终的承诺,所以一个更现实的用例是:
walkTree("grandpa", function(node) console.log("visiting " + node.name); )
.then(function() console.log("finished walking the tree"));
【问题讨论】:
你的想法是好的和直截了当的。walkTree
方法可以批量处理从访问每个节点返回的承诺,然后当所有这些承诺都已实现时,它会使用承诺数组或您决定它应该解决的任何内容来解决(这样您就可以访问长度,不确定您还想用它做什么)。这将做类似于RSVP.all
可能做的事情(在等待一批承诺解决或如果其中一个拒绝时拒绝承诺)。
您的要点正是正确的方法。唯一的问题是您在 map
回调中错过了 return
声明!
【参考方案1】:
嗯,处理每个节点的函数调用不是什么大问题,但是收集节点值是个问题。走那棵树有点困难,最好的办法是将它映射到没有最终值的树上。你可以使用类似的东西:
function buildTree(root_name)
var prom = getNodeAsync(root_name);
return Promise.all([prom, prom.then(function(n)
return Promise.all(childrenNamesOf(n).map(child => buildTree(child)))
)]);
从那里开始:
var flatTree = buildTree(root_name).then(flatArray);
flatTree.then(nodes => nodes.forEach(visit_fn));
flatTree.then(nodes => whateverYouWantToDoWithNodes);
要展平您可以使用的数组:
function flatArray(nodes)
if(Array.isArray(nodes) && nodes.length)
return nodes.reduce(function(n, a)
return flatArray(n).concat(flatArray(a));
);
else
return Array.isArray(nodes) ? nodes : [nodes];
老实说,如果你想要一个节点列表,那么使用 tree walker 是没有意义的,你最好将它展平然后迭代元素,但是如果你愿意,你可以遍历数组树。
【讨论】:
你是对的:这是建立列表的折磨方式。事实上,我真的不打算使用最终的 Promise() 作为一种扁平化结果的方式——我只想要一个在遍历整个树后完成的 Promise。你的回答就是这样。 (不过,我确实打算访问沿途的每个节点。)打勾。 Hrm,之前的数组展平功能有一些问题,这个应该可以工作。 不用担心。 FWIW,您可以消除 buildTree 方法中的 if 子句,因为“一个空的承诺就是一个履行的承诺”。请参阅下面的答案。 @fearless_fool,我不确定当它没有孩子时,你会从childrenNamesOf
获得什么样的价值。如果它返回一个空数组,那么可以确定,你可以把它拿走。如果它返回 undefined
或 null
或不是可迭代的东西,那么它将拒绝承诺。【参考方案2】:
尽管我在 O.P 中说过,我并不真正关心最终 Promise 的返回值,但我确实想等到所有节点都被遍历完。
最初尝试的问题只是 map() 函数中缺少返回语句。 (尽管看起来,这在结构上与@MinusFour 的答案基本相同。)以下更正形式:
function walkTree(node_name, visit_fn)
return getNodeAsync(node_name)
.then(function(node)
visit_fn(node);
var child_names = childrenNamesOf(node);
var promises = child_names.map(function(child_name)
return walkTree(child_name, visit_fn);
);
return Promise.all(promises);
);
;
下面是 walkTree() 的两个用例。第一个简单地按顺序打印节点,然后在树遍历完成时宣布:
walkTree("grandpa", function(node) console.log("visiting " + node.name); )
.then(function() console.log("finished walking the tree"));
第二个创建一个平面节点列表,在树遍历完成时可用:
var nodes = [];
walkTree("grandpa", function(node) nodes.push(node) )
.then(function() console.log('found', nodes.length, 'nodes);
console.log('nodes = ', nodes); );
【讨论】:
以上是关于带着承诺走一棵树的主要内容,如果未能解决你的问题,请参考以下文章