[英]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.