我如何recursion和asynchronous构build一个未知大小的树?

我试图检索我的网站给定的职位上的评论,但我无法build立嵌套的评论,因为node.js的asynchronous性质。

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

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

我试图跟踪recursion调用,并结束时,有0个电话留下来,但这是错误的,有时会根据树结构提前返回。

你可以保持工作的计数,当它达到零时,你调用调用者的callback函数。 recursion树中每个执行函数的实例都将定义自己的callback函数,这样只有顶层实例所做的调用才会在第一个语句中调用callback函数(函数体外):

 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来模拟asynchronousgetChildComments

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