如何在递归函数中处理JavaScript中的回调?

时间:2015-08-24 18:41:04

标签: javascript google-chrome recursion bookmarks tree-traversal

尝试比较Chrome中的两个书签子树,我遇到了异步API调用的问题,以查询书签文件夹的子项。

function titleComparator (lhs, rhs) {
  return lhs.title < rhs.title ? -1 : lhs.title > rhs.title ? 1 : 0;
}

// Return whether two bookmark trees have equal content
function compare(lhs, rhs) {
  // Not equal if one is a bookmark and another is a folder
  if (('url' in lhs) != ('url' in rhs))
    return false;
  // If both are bookmarks, compare url and title
  if ('url' in lhs && 'url' in rhs)
    return lhs.title == rhs.title && lhs.url == rhs.url;
  // If both are folders, compare contents
  chrome.bookmarks.getChildren(lhs.id, function (lhsChildren) {
    chrome.bookmarks.getChildren(rhs.id, function (rhsChildren) {
      if (lhsChildren.length != rhsChildren.length)
        return false;  // Want to return from compare()
      lhsChildren.sort(titleComparator);
      rhsChildren.sort(titleComparator);
      for (var i = 0; i < lhsChildren.length; i++)
        if (!compare(lhsChildren[i], rhsChildren[i])
          return false;  // Same here
      return true;  // Same here
    });
  });
}

如何在递归函数中处理JavaScript中的回调?

3 个答案:

答案 0 :(得分:0)

  

return will only ever exit the callee

你必须提供一个回调,以便异步地吐出答案。

你曾经写过return语句的任何地方,callback关闭,你应该标准化,而不是将它传递给你的回调。

function compareAsync(lhs, rhs, callback) {
  //…
    callback(false); return;
  //…
    callback(lhs.title == rhs.title && lhs.url == rhs.url); return;
  //…
        callback(false); return;  // Want to return from compare()
  //…

  var finished = 0;
  var whetherSuccess = true;
  lhsChildren.forEach(function(iterand, index) {
    compareAsync(iterand, rhsChildren[index], function(result) {
      whetherSuccess &= result;
      if (++finished === lhsChildren.length) {
        callback(whetherSuccess);
      }
    });
  });
}

所以:在找到lhsChildren是什么时,我们开始了一堆异步函数。他们会在某个时刻递增finished个计数器。他们每个人都有能力通过whetherSuccess将整体false降级为&=。发现他们是获得答案的最终功能的消费者是将whetherSuccess报告给原始callback的人。

答案 1 :(得分:0)

as explained in detail here

您需要重构代码。

不知何故,似乎在异步(通常是延迟的)函数中使用递归来搜索基于树的或分层数据模型的方法不正确。

我认为这应该是这样做的方法:

  1. 将逻辑分成几个函数
  2. 使用“延迟加载”以避免重复调用getChilden()
  3. 使用递归并定义新的嵌套函数回调
  4. 将for循环重构为递归
  5. 请参阅我未经测试的代码以显示我的意思:

       function compare(lhs, rhs, callback, index, lhsChilds, rhsChilds){
    
        // Not equal if one is a bookmark and another is a folder
        if (('url' in lhs) != ('url' in rhs)) {
            callback(false);
            return;
        }
        // If both are bookmarks, compare url and title
        if ('url' in lhs && 'url' in rhs) {
            callback(lhs.title == rhs.title && lhs.url == rhs.url);
            return;
        }
    
        // If both are folders, check parameters and compare contents
    
    
        //First, check if the list has already been loaded (code is running inside a recursive step)
        if(lhsChilds != undefined && rhsChilds != undefined){
            compareTwoChilds(lhs, rhs, callback, index, lhsChilds, rhsChilds);
        }
        else{
            index = 0; //first recursion for this tuple (lhs, rhs)
            chrome.bookmarks.getChildren(lhs.id, function (lhsChildren) {
                chrome.bookmarks.getChildren(rhs.id, function (rhsChildren) {
                    compareTwoChilds(lhs, rhs, callback, index, lhsChilds, rhsChilds);
                });
            });
        }
    
    }
    
    function compareTwoChilds(lhs, rhs, callback, index, lhsChilds, rhsChilds){
        if (index < lhsChildren.length){ //just for the safety
            if (lhsChildren.length != rhsChildren.length) {
                callback(false);
                return;
            }
            lhsChildren.sort(titleComparator);
            rhsChildren.sort(titleComparator);
    
            //compare using recursion, with an emtpy lists of rhs and lhs children
            compare(lhsChildren[index], rhsChildren[index], function(compareResult){
                if(!compareResult){
                    callback(false); //if the result is false, no more search needed
                }else{ // use recursion again to loop through the next childs using the already loaded childs
                    if (++index < lhsChildren.length){
                        compare(lhsChildren[index], rhsChildren[index], callback, index, lhsChilds, rhsChilds)
                    }else{
                        callback(true); // the loop has ended,
                    }
                }
            });
    
        }else{
            callback(false); //this should never happen, so not the same...
        }
    
    }
    

    您可以像这样调用比较函数:

    compare(lhs,rhs, function(result){
       var compareResult = result;
       //carry on with your code here
    });
    //and not here :-)
    

答案 2 :(得分:0)

首先,我发现Chrome还具有getSubTree()功能,这使得事情变得相当容易。因此,如果您只是想让它工作,请使用它而不是异步地逐节点遍历树节点。但是,这仍然是一个有趣的问题,感谢a reddit user,我找到了一个有效的解决方案。

compare()是递归调用自身的主函数。但是,由于内部的异步调用,它不能消耗它的递归调用的返回值。

// Pass a boolean to the callback indicating whether the recursive contents of
// both bookmarks folders are equal.
function compare(lhs, rhs, callback) {
    // Compare titles except for the top-level folder
    if (lhs.parent_ && lhs.title !== rhs.title) {
        compare_failure(callback);
        return;
    }
    // Compare urls if at least one of the sides is a bookmark
    if ('url' in lhs || 'url' in rhs) {
        if ((lhs.url || null) === (rhs.url || null))
            compare_respond(lhs.parent_, callback);
        else
            compare_failure(callback);
        return;
    }
    // For both sides being folders, we have to take a look at the contents
    chrome.bookmarks.getChildren(lhs.id, function (lhs_children) {
        chrome.bookmarks.getChildren(rhs.id, function (rhs_children) {
            // Compare amount of children
            if (lhs_children.length != rhs_children.length) {
                compare_failure(callback);
                return;
            }
            // Keep track of how many children already reported back
            lhs.all_children = lhs_children.length;
            lhs.equal_children = 0;
            // Let pairs of children compare each other
            lhs_children.sort(bookmark_comparator);
            rhs_children.sort(bookmark_comparator);
            for (var i = 0; i < lhs_children.length; i++) {
                var lhs_child = lhs_children[i];
                var rhs_child = rhs_children[i];
                // Store parent reference so the deeper function can
                // asynchronously respond with the results once finished.
                lhs_child.parent_ = lhs;
                compare(lhs_child, rhs_child, callback);
            }
        });
    });
};

compare_respond()是用于传播更深层节点结果的对应物。它在上面的主函数中用来代替返回。

// Report comparison results back to the parent node. The parent node waits
// until it collected the results from all its children. Then it reports to
// its parent in turn. At the root node, the user callback is executed.
function compare_respond(node, callback) {
    // Collect child results
    node.equal_children++;
    // Respond upwards if we got results from all
    if (node.equal_children == node.all_children) {
        if ('parent_' in node)
            compare_respond(node.parent_, callback);
        else
            callback(true);
    }
};
当我们找到一对不相等的节点时,

compare_failure()用于在任何时候中止整个事情。在这种情况下,我们不必向上报告。

// Break out of the recursive function and report failure to the user. It's
// safe against being called multiple times so multiple children can report
// failure and the user will only be notified once.
function compare_failure(callback) {
    if ('called' in callback)
        return;
    callback.called = true;
    callback(false);
};

bookmark_comparator()是一个小助手,用于对子书签数组进行排序。需要排序来比较两个文件夹的内容,因为我不想依赖项目顺序。

// Comparator to sort lists of bookmark nodes first by title and second by
// url. Take into that folders have to url.
function bookmark_comparator(lhs, rhs) {
    if (lhs.title != rhs.title)
        return lhs.title < rhs.title ? -1 : 1;
    if (lhs.url || null != rhs.url || null)
        return lhs.url || null < rhs.url || null ? -1 : 1;
    return 0;
};