当结果异步可用时,函数 searchNode 将需要返回一个 Promise。
但是在两个地方你没有这样做:
你应该返回第一次调用tree.getChildren
-
searchNode 的(递归)返回值应该作为一个 Promise 处理,而你却把它当作一个同步结果:
childPath = searchNode(searchValue, path+"/"+children[i]);
if (childPath != null) { // ...etc.
这是不可能的。返回值应该是一个promise,所以你需要在它上面调用then方法来获取返回值。
由于您需要迭代几个,也许是所有的孩子,您会得到尽可能多的承诺。但是你可以而且应该只返回一个承诺。
虽然你可以在找不到值的情况下返回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 方法来监控哪个是第一个解析的。
为此,您实际上可以定义两个通用实用程序方法——Promise.not 和 Promise.some——就像已经有 Promise.race 和 Promise.all。这些功能可以在"Resolve ES6 Promise with first success?"等其他问答中找到:
// 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's 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');
});