【问题标题】:How to handle callbacks in JavaScript within recursive functions?如何在递归函数中处理 JavaScript 中的回调?
【发布时间】:2015-08-24 18:41:04
【问题描述】:

尝试在 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 中的回调?

【问题讨论】:

  • 我错过了asynchronous

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


【解决方案1】:

as explained in detail here

你需要重构你的代码。

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

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

  1. 将逻辑分成几个函数
  2. 使用“延迟加载”避免重复调用 getChilden()
  3. 使用递归并定义一个新的嵌套函数回调
  4. 也将 for 循环重构为递归

查看我未经测试的代码以说明我的意思:

   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 :-)

【讨论】:

  • 我猜最后一个是callback(true)?如果 compare() 不再返回布尔值,if (!compare(lhsChildren[i], rhsChildren[i], callback)) 行如何工作?
  • 你是对的。这是棘手的部分......我会在得到解决方案后立即更新我的答案
  • Puhhh,艰难的一个。我希望我的回答是一个启发。我会尝试找出是否有办法一次请求整个书签树。如果你有 ajax 后端,你就不会要求所有的树级别都加一......我希望至少我的回答能引导你得到 100% 的答案;。)
【解决方案2】:

return will only ever exit the callee

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

在您编写了旨在供 callback 闭包使用的 return 语句的任何地方,您都应该标准化,而不是将其传递给您的回调。

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 计数器。他们每个人都有权通过&amp;= 将整体whetherSuccess 降级为false。发现他们是获得答案的最终功能的消费者是将whetherSuccess报告给原始callback的人。

【讨论】:

  • 递归对这个有用吗? compare() 调用自身以遍历树。
  • 天哪,你是对的;我错过了。您应该再次调用compareAsync,而不是调用回调,并将一个回调传递给它——当被调用时——使用false调用你的原始回调。 compareAsync(lhsChildren[i], rhsChildren[i], callback.bind(null, false)); return; // Same here
  • 嵌套的compare() 调用的结果并不是用户的返回值,而是compare() 的父调用的返回值。
  • 啊哈,我看到兔子洞越来越深了。好的,我会编辑一个更狡猾的答案。
  • @danijar 查看我昨天的更新;我相信这可以解决问题。
【解决方案3】:

首先,我发现 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() 是用于传播更深节点的结果备份的对应项。在上面的 main 函数中使用它来代替 return。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-02-24
    • 1970-01-01
    • 2010-09-21
    • 2020-09-07
    • 2020-06-25
    • 2017-07-28
    • 2019-02-20
    • 2021-07-09
    相关资源
    最近更新 更多