我如何递归地和异步地构建一个未知大小的树?

时间:2016-07-13 20:10:17

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

我试图在我的网站的特定帖子上检索我的评论,但由于node.js的异步性质,我无法构建嵌套评论。

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个调用时结束,但这有点错误,有时会根据树结构提前返回。

1 个答案:

答案 0 :(得分: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(与没有父项的注释的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 执行查询:这可能需要相对较长的时间才能完成,并且会对数据库引擎造成相当大的负担。

执行单个查询以检索整个注释层次结构可能更有效。