【问题标题】:How can I recursively and asyncronously build a tree of unknown size?如何递归和异步构建未知大小的树?
【发布时间】:2016-11-16 14:00:19
【问题描述】:

我正在尝试在我的网站的给定帖子中检索我的 cmets,但由于 node.js 的异步性质,我无法构建嵌套 cmets。

getBlock([], function(){});    

function getBlock(comments, callback) {
    comments.forEach(function(comment) {
        getChildComments(comment, function(err, children) {
            if (children) {
                getBlock(children, callback);
            }
            comment.Comments = children;

            /* not sure how to decide when to be done?*/
            callback(null, comments);
        });
    });
}

上面的代码适用于同步代码,但不适用于异步代码,因为我无法判断comments 何时包含要返回到浏览器的所有数据。

我试图跟踪递归调用,并在剩下 0 个调用时结束,但这有问题,有时会根据树结构提前返回。

【问题讨论】:

  • 你好 Jack,在 DB 中有结构不是更好吗:idComment、idParentComment、idArticle、userName、userComment、dateAdd 或类似的东西......而不是只为给定选择所有 cmets idArticle 只使用一个数据库查询而不是只排序一次?
  • @JanJůna 这就是我之前尝试做的事情,尽管我认为我受到 ORM 的限制。 cmets 是嵌套的,并且 cmets 可以有自己的 cmets。我正在使用 sequelize 和 postgresql
  • 您可以在 sequelize 中进行原始查询 - 请参阅:docs.sequelizejs.com/en/latest/docs/raw-queries 假设您的页面将被 100 个用户请求,您将运行所有这些递归查询一百次(对于每个客户端)将耗尽您数据库的资源..
  • 您确实应该尝试在一两次行程中查询此数据,而不是递归循环。对于一个非常大的评论线程,这可能是一个巨大的性能瓶颈。

标签: javascript node.js asynchronous recursion sequelize.js


【解决方案1】:

你可以保留一个计数仍然要做的工作,当它达到零时,你调用调用者的回调函数。递归树中执行函数的每个实例都会定义自己的回调,这样只有顶层实例的调用才会调用第一条语句中的回调(函数体之外):

function getBlock(comments, callback) {
    if (!comments || !comments.length) {
        // Nothing to do, call back synchronously
        callback(comments);
        return;
    }
    var leftOver = comments.length;
    comments.forEach(function(comment) {
        getChildComments(comment, function(err, children) {
            comment.Comments = children;
            // provide custom callback:
            getBlock(children, function () {
                // only call parent's callback when all is done here:
                if (--leftOver === 0) callback(comments);
            });
        });
    });
}

与您的示例代码不同,上述代码不能使用空数组调用,而应使用要检索其下方层次结构的注释对象数组。要获取所有内容,您将传递一个包含一个虚拟评论对象的数组,该对象将具有未定义的id(与没有父级的 cmets 的 parentId 引用匹配)。像这样的:

getBlock([container], function(){
    console.log(container);
});

下面是一个工作实现,它使用模拟数据和setTimeout来模拟异步getChildComments

function Comment(id, text, parentId) {
    this.id = id;
    this.text = text;
    this.parentId = parentId;
}

var mockData = [
    new Comment(1, "Michal Jackson died today"),
    new Comment(2, "How did he die?", 1),
    new Comment(3, "His doctor gave him too much of the white stuff", 2),
    new Comment(4, "He died in his sleep", 2),
    new Comment(5, "Oh my god, this can't be true!?", 1),
    new Comment(6, "He will be greatly missed", 1),
    new Comment(7, "I am working in my garden"),
    new Comment(8, "Happy birthday, friend!"),
    new Comment(9, "Thank you!", 8),   
];

function getChildComments(parentComment, callback) {
    // Mock asynchronous implementation, for testing the rest of the code
    setTimeout(function () {
        var children = mockData.filter(function (comment) {
            return comment.parentId === parentComment.id;
        });
        callback(null, children); 
    }, 0);
}

var container = new Comment(); // dummy node to collect complete hierarchy into
getBlock([container], function(){
    console.log(container);
});

function getBlock(comments, callback) {
    if (!comments || !comments.length) {
        // Nothing to do, call back synchronously
        callback(comments);
        return;
    }
    var leftOver = comments.length;
    comments.forEach(function(comment) {
        getChildComments(comment, function(err, children) {
            comment.Comments = children;
            // provide custom callback:
            getBlock(children, function () {
                // only call parent's callback when all is done here:
                if (--leftOver === 0) callback(comments);
            });
        });
    });
}

性能考虑

以上是对“如何递归异步构建未知大小的树”的直接回答,但这可能不是获得最终结果的最有效方法。

您从 Postgres 数据库中获取数据,并且可能为每次调用 getChildComments 执行查询:这可能需要相对较长的时间才能完成,并且会给您的数据库引擎带来相当大的负载。

执行单个查询来检索整个 cmets 层次结构可能更有效。

【讨论】:

  • 在考虑了您的性能考虑部分后,我将编写自己的自定义查询。感谢您的帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-07-02
  • 2021-02-09
  • 1970-01-01
  • 2016-03-05
  • 1970-01-01
  • 1970-01-01
  • 2012-10-22
相关资源
最近更新 更多