[英]search node in a tree using recursive javascript promises
我陷入了基于Java Promise的程序中,却无法弄清楚如何使它以Promise格式返回正确的值。
给定一个Tree API,该API将节点的子级返回为PROMISE。 例如:
tree.getChildren('/2/4')
.then(function (nodes) {
console.log(nodes); // logs 7,8,9 as children of node 4
}
使用tree.getChildren
方法, searchNode
方法应递归尝试在树中查找searchValue
并返回其路径(如果找到),否则返回null
。
下面的方法只是尝试在树路径中搜索节点,但是它只是返回undefined
,我认为这是由于该方法的异步特性所致。 我该如何重写代码以兑现承诺并获得期望的行为?
function searchNode(searchValue, path){
tree.getChildren(path).then(function(children){
if(children.length>0){
for(var i = 0; i<children.length; i++){
if(children[i] == searchValue){
return path;
} else {
childPath = searchNode(searchValue, path+"/"+children[i]);
if(childPath != null)
return childPath;
}
}
}
else{
return null;
}
})
}
由于结果变为异步可用,因此函数searchNode将需要返回Promise。
但是在两个地方,您不会这样做:
您应该将第一个调用tree.getChildren
给tree.getChildren
应该将searchNode
的(递归)返回值作为promise处理,但是您将其视为同步结果:
childPath = searchNode(searchValue, path+"/"+children[i]); if (childPath != null) { // ...etc.
这是不可能的。 返回值应该是一个承诺,因此您需要在其上调用then
方法以获得返回值。
当您需要迭代几个甚至所有的孩子时,您将获得尽可能多的诺言。 但是您可以并且应该只返回一个承诺。
尽管在找不到null
的情况下可以返回null
,但在我看来,这更符合在这种情况下产生拒绝的诺言的诺言的思想。
因此,您可以获得第一个承诺的结果,然后,如果它解决了,就返回那个承诺,但是如果它拒绝了(即未找到),则应该链接下一个承诺,再连接下一个,直到一个解决,并且退还那个。 如果他们都没有解决,或者没有孩子,应退回被拒绝的承诺。
这是建议的代码:
function searchNode(searchValue, path){
return tree.getChildren(path).then(function(children){
// First look for the value among the immediate children
for(let i = 0; i<children.length; i++){
if(children[i] == searchValue){
return path;
}
}
// Then look deeper
return (function loop(i) {
if (i >= children.length) {
return Promise.reject("not found");
} else {
// after asynchronous result comes in, either
// continue the loop, or resolve with path
return searchNode(searchValue, path+"/"+children[i])
.catch(loop.bind(null, i+1));
}
})(0);
}
"use strict"; // Simple implementation of tree const tree = { root: { '2': { '4': { '1': {} }, '7': {} }, '0': {} }, getChildren: function(path) { return new Promise(function (resolve) { const node = path.split('/').reduce(function (parent, node) { return node === '' || !parent ? parent : parent[node]; }, tree.root); resolve(Object.keys(node)); }); } }; function searchNode(searchValue, path){ return tree.getChildren(path).then(function(children){ // First look for the value in the immediate children for(let i = 0; i<children.length; i++){ if(children[i] == searchValue){ return path; } } // Then look deeper return (function loop(i) { if (i >= children.length) { return Promise.reject("not found"); } else { // after asynchronous result comes in, either // continue the loop, or resolve with path return searchNode(searchValue, path+"/"+children[i]) .catch(loop.bind(null, i+1)); } })(0); }) } // Demo searchNode('1', '').then(function (path) { console.log(path); }, function (reason) { console.log(reason); });
上面的解决方案不会在子级中并行显示。 相反,它将决定一个节点的搜索结果,然后再决定是否为下一个同级节点启动搜索。 根据tree.getChildren
实现的真正异步程度,效率可能较低:
想象一棵树,其中第一个子节点具有1000个节点的多级子树,而要查找的值是第二个子节点的直接后代。 如果将并行启动搜索,那么您期望这种情况下的结果会更快。 另一方面,一旦找到该值,上述代码将不会搜索其他同级节点,而使用并行搜索,即使在找到该值并且主要承诺是之后,搜索仍将在“后台”继续进行(异步)解决。 因此,我们应该确保在找到该值时不会启动更深层的搜索。
为了实现这种并行搜索的想法,您将立即在所有子级上启动searchNode , then
在每个子级上应用then
方法以监视哪个解析为第一个。
为此,您实际上可以定义两个通用实用程序方法Promise.not
和Promise.some
,就像已经存在Promise.race
和Promise.all
。 这些功能可以在其他问答中找到,例如“是否成功解决ES6 Promise?” :
// Invert the outcome of a promise
Promise.not = promise => promise.then(x => {throw x}, e => e);
// Resolve when the first promise resolves
Promise.some = iterable => Promise.not(Promise.all(iterable.map(Promise.not)));
或者,您可以为此使用库解决方案,例如bluebird的Promise.any
。
然后,您将需要添加一些机制,以在已经用发现的值解决了主要承诺后停止启动更深入的搜索。 为此,只需在解决时监听主要的诺言和标志即可。 这可用于停止异步代码以启动任何进一步的搜索。
这是您在情况下如何使用Promise.some
函数的方法:
function searchNode(searchValue, path){
let resolved = false;
return (function searchNodeSub(path) {
return tree.getChildren(path).then(function(children){
// If we already found the value via another parallel search, quit
return resolved ? true
// Otherwise look for the value among the immediate children
: children.some(child => child == searchValue) ? path
// If not found there, look deeper -- in parallel
: Promise.some(children.map(child => searchNodeSub(path+"/"+child)));
})
})(path).then( path => (resolved = true, path) ); // register that we have a result
}
注意children.some
和Promise.some
之间的相似性。
"use strict"; // Simple implementation of tree const tree = { root: { '2': { '4': { '1': {} }, '7': {} }, '0': {} }, getChildren: function(path) { return new Promise(function (resolve) { let node = path.split('/').reduce(function (parent, node) { return node === '' || !parent ? parent : parent[node]; }, tree.root); resolve(Object.keys(node)); }); } }; // Invert the outcome of a promise Promise.not = promise => promise.then(x => {throw x}, e => e); // Resolve when the first promise resolves Promise.some = iterable => Promise.not(Promise.all(iterable.map(Promise.not))); function searchNode(searchValue, path){ let resolved = false; return (function searchNodeSub(path) { return tree.getChildren(path).then(function(children){ // If we already found the value via another parallel search, quit return resolved ? true // Otherwise look for the value among the immediate children : children.some(child => child == searchValue) ? path // If not found there, look deeper -- in parallel : Promise.some(children.map(child => searchNodeSub(path+"/"+child))); }) })(path).then( path => (resolved = true, path) ); // register that we have a result } // Demo searchNode('1', '').then(function (path) { console.log(path); }, function (reason) { console.log('Not found'); });
您需要在searchNode
返回.getChildren()
承诺,以便稍后可以在返回childPath
时等待它解决:
function searchNode(searchValue, path){
return tree.getChildren(path).then(function(children){ //return the promise so we can later wait for it to resolve
if(children.length>0){
for(var i = 0; i<children.length; i++){
if(children[i] == searchValue){
return path;
} else {
return searchNode(searchValue, path+"/"+children[i])
.then(function(childPath){ //wait for searchNode to complete
if (childPath != null) {
return childPath;
}
});
}
}
}
else{
return null;
}
})
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.