简体   繁体   English

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

[英]search node in a tree using recursive javascript promises

I am stuck in Javascript promise based program and not able to figure out how to get it to return the right value, in a promise format. 我陷入了基于Java Promise的程序中,却无法弄清楚如何使它以Promise格式返回正确的值。

Given an Tree API that returns children of a node as PROMISE. 给定一个Tree API,该API将节点的子级返回为PROMISE。 Eg: 例如:

tree.getChildren('/2/4')
.then(function (nodes) {    
    console.log(nodes); // logs 7,8,9 as children of node 4
}

using tree.getChildren method, the searchNode method should recursively try to look for searchValue in the tree and return its path if found, null otherwise. 使用tree.getChildren方法, searchNode方法应递归尝试在树中查找searchValue并返回其路径(如果找到),否则返回null

The method below, simply tries to search for the node in a tree path, but it is just returning undefined , which I believe is due to async nature of the method. 下面的方法只是尝试在树路径中搜索节点,但是它只是返回undefined ,我认为这是由于该方法的异步特性所致。 How do I rewrite the code to honour promises and get desired behaviour? 我该如何重写代码以兑现承诺并获得期望的行为?

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;
        }
    })
}

As the result becomes available asynchronously, the function searchNode will need to return a Promise. 由于结果变为异步可用,因此函数searchNode将需要返回Promise。

But at two places you do not do this: 但是在两个地方,您不会这样做:

  1. You should return the first call to tree.getChildren 您应该第一个调用tree.getChildrentree.getChildren

  2. The (recursive) return value of searchNode should be handled as a promise, yet you treat it as a synchronous result: 应该将searchNode的(递归)返回值作为promise处理,但是您将其视为同步结果:

      childPath = searchNode(searchValue, path+"/"+children[i]); if (childPath != null) { // ...etc. 

    This is not possible. 这是不可能的。 The return value should be a promise, and so you would need to call the then method on it to get the return value. 返回值应该是一个承诺,因此您需要在其上调用then方法以获得返回值。

As you need to iterate several, maybe all, of the children, you would get as many promises back. 当您需要迭代几个甚至所有的孩子时,您将获得尽可能多的诺言。 But you can and should only return one promise. 但是您可以并且应该只返回一个承诺。

Although you could return null in case of not finding the value, it is in my opinion more in line with the idea of promises to produce a rejected promise in that case. 尽管在找不到null的情况下可以返回null ,但在我看来,这更符合在这种情况下产生拒绝的诺言的诺言的思想。

So you could get the result of the first of those promises and then if it resolves, return that promise, but if it rejects (ie not found) you should chain the next promise, and the next, ... until one resolves, and return that one. 因此,您可以获得第一个承诺的结果,然后,如果它解决了,就返回那个承诺,但是如果它拒绝了(即未找到),则应该链接下一个承诺,再连接下一个,直到一个解决,并且退还那个。 If none of them resolve, or there are no children, a rejected promise should be returned. 如果他们都没有解决,或者没有孩子,应退回被拒绝的承诺。

Here is the suggested code: 这是建议的代码:

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); }); 

A Parallel search alternative 并行搜索替代

The above solution will not look among the children in parallel. 上面的解决方案不会在子级中并行显示。 Instead it will wait for the search result for one node before deciding whether the search should be launched for the next sibling node. 相反,它将决定一个节点的搜索结果,然后再决定是否为下一个同级节点启动搜索。 Depending on how asynchronous the tree.getChildren implementation really is, this may be inefficient: 根据tree.getChildren实现的真正异步程度,效率可能较低:

Imagine a tree where the first child node has a multi-level sub-tree of 1000 nodes, while the value being looked for is an immediate descendant of the second child node. 想象一棵树,其中第一个子节点具有1000个节点的多级子树,而要查找的值是第二个子节点的直接后代。 If the search would be launched in parallel, you'd expect the result sooner for such a scenario. 如果将并行启动搜索,那么您期望这种情况下的结果会更快。 On the other hand, the above code will not search other sibling nodes once the value has been found, while with a parallel search the search would continue in the "background" (asynchronously) even after the value has been found and the main promise is resolved. 另一方面,一旦找到该值,上述代码将不会搜索其他同级节点,而使用并行搜索,即使在找到该值并且主要承诺是之后,搜索仍将在“后台”继续进行(异步)解决。 So we should make sure that no deeper searches are initiated when the value has been found. 因此,我们应该确保在找到该值时不会启动更深层的搜索。

In order to implement this parallel search idea you would launch searchNode on all children immediately, and apply the then method on each to monitor which one resolves as the first. 为了实现这种并行搜索的想法,您将立即在所有子上启动searchNodethen在每个子上应用then方法以监视哪个解析为第一个。

For this you could in fact define two generic utility methods -- Promise.not and Promise.some --, much like there already are Promise.race and Promise.all . 为此,您实际上可以定义两个通用实用程序方法Promise.notPromise.some ,就像已经存在Promise.racePromise.all These functions can be found in other Q&A like "Resolve ES6 Promise with first success?" 这些功能可以在其他问答中找到,例如“是否成功解决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)));

Or, you could use a library solution for this, like bluebird's Promise.any . 或者,您可以为此使用库解决方案,例如bluebird的Promise.any

Then, you would need to add some mechanism to stop initiating deeper searches when the main promise has already been resolved with the found value. 然后,您将需要添加一些机制,以在已经用发现的值解决了主要承诺后停止启动更深入的搜索。 For this it suffices to listen to the main promise and flag when it resolves. 为此,只需在解决时监听主要的诺言和标志即可。 This can be used to stop the asynchronous code to initiate any further searches. 这可用于停止异步代码以启动任何进一步的搜索。

Here is how you would use that Promise.some function in your case: 这是您在情况下如何使用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
}

Note the similarity between children.some and Promise.some . 注意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'); }); 

You need to return the .getChildren() promise in searchNode so that you can later wait for it to resolve when returning the childPath : 您需要在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