繁体   English   中英

使用递归javascript Promise在树中搜索节点

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

但是在两个地方,您不会这样做:

  1. 您应该第一个调用tree.getChildrentree.getChildren

  2. 应该将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个节点的多级子树,而要查找的值是第二个子节点的直接后代。 如果将并行启动搜索,那么您期望这种情况下的结果会更快。 另一方面,一旦找到该值,上述代码将不会搜索其他同级节点,而使用并行搜索,即使在找到该值并且主要承诺是之后,搜索仍将在“后台”继续进行(异步)解决。 因此,我们应该确保在找到该值时不会启动更深层的搜索。

为了实现这种并行搜索的想法,您将立即在所有子上启动searchNodethen在每个子上应用then方法以监视哪个解析为第一个。

为此,您实际上可以定义两个通用实用程序方法Promise.notPromise.some ,就像已经存在Promise.racePromise.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.somePromise.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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM