簡體   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