我有一个等级制度。一个类别具有parentId的(这是0,如果它是一个根类别)。
示例行中的类别的DB:
name | id | parentid
-----------+------+----------
Label | 71 | 8
我需要给定类别的所有子类别。通过将所有的,不仅意味着一个类别的直接孩子,但所有的孩子的每一个孩子。
我以前做过递归的东西,但在同步环境中,它中风我,这不正是相同的方式工作。
我知道我不是远离工作的解决方案,但它并不适用于所有的情况还没有工作。注意:删除所有调试日志行不杂乱的问题。
此外,任何简化和/或优化的欢迎。举例来说,我不喜欢有两个回调,递归一个,最后一个......(但也许由于异步它需要这样吗?)。
整个事情应该返回给定类别的所有子类别的ID数组(allCats
)。
该解决方案目前已经为单一类别没有孩子,和层次结构的下一层(与allCats
正确地包含所有的ID)。在失败两个层次(最终回调永远不会被调用,所以cnt
没有被正确地更新?)。
搜索是通过调用Category.getAllSubCategories(categoryId);
拉开序幕
Category.getSubCategories = function(cat, cnt, fullCats, cb, finalCb) {
Category.find({where: {parentId: cat.id}}, function(err, cats) {
if (err) {
cb(err);
}
if (cats.length > 0) {
var ids = [];
for (var i=0; i<cats.length; i++) {
ids.push(cats[i].id);
}
fullCats = fullCats.concat(ids);
cb(null, cnt, fullCats, cats, finalCb);
} else {
if (cnt > 0) cnt -= 1;
cb(null, cnt, fullCats, null, finalCb);
}
});
}
var catSearchCallback = function(err, cnt, fullCats, cats, finalCb) {
if (err) {
finalCb(err);
}
if (cats) {
for (var c=0; c<cats.length; c++) {
cnt += 1;
Category.getSubCategories(cats[c], cnt, fullCats, catSearchCallback, finalCb);
}
} else {
if (cnt == 0) {
finalCb(null, fullCats);
}
}
}
/* start here */
Category.getAllSubCategories = function(categoryId, cb) {
Category.findById(categoryId, function(err, cat) {
if (err) {
return logger.error(err);
}
var fullCats = []; //collection holding ALL ids
var cnt = 0; //counter to count how many steps we have done
if (cat) {
fullCats.push(categoryId); //the category in question needs to be in the results as well
Category.getSubCategories(cat, cnt, fullCats, catSearchCallback, function(err, allCats) {
if (err) {
cb(err);
}
cb(null, allCats);
});
} else {
return categoryId;
}
});
}
下面似乎是工作,我测试了它在我的系统的零,一个和两个水平层次和它(到目前为止...)的期望是什么。
当然有可能更优雅的解决方案,更有效的解决方案,等你都非常欢迎,如果你有一个分享。对于我来说,暂时,这个工程:)
/**
* Recursive iteration functions for getting subcategories
*
* Starts with getAllSubCategories, and recursively call
* - getSubCategories
* - which in turn calls catSearchCallback as callback
*
* In order to avoid race conditions, we can't use a global variable...
* ...thus we need to pass the cumulated results (fullCats) and the
* running queue (queue) as parameters to all involved functions.
* The final "done" callback is also passed around until it's needed
*/
Category.getSubCategories = function(cat, queue, fullCats, cb, done) {
//load all subcategories of this the provided category is parent
Category.find({where: {parentId: cat.id}}, function(err, cats) {
if (err) {
logger.error(err);
cb(err);
}
if (cats.length > 0) {
cb(null, queue, fullCats, cats, cat.id, done);
} else {
cb(null, queue, fullCats, null, cat.id, done);
}
});
}
/**
* callback after every subCategory
*/
var catSearchCallback = function(err, queue, fullCats, cats, catId, done) {
if (err) {
logger.error(err);
done(err);
}
//first remove the returned category ID from the queue (means it has been processed)
var index = queue.indexOf(catId);
if (index > -1) {
queue.splice(index, 1);
} else {
//this should NOT HAPPEN!!!!
logger.warn("NO CAT FOUND FOR REMOVAL");
}
//now if there are subcategories in this category, go further down
if (cats) {
for (var c=0; c<cats.length; c++) {
//add this ID to the queue
queue.push(cats[c].id);
//add this ID to the final results
fullCats.push(cats[c].id);
//iterate this category
Category.getSubCategories(cats[c], queue, fullCats, catSearchCallback, done);
}
} else {
//there are no further subcategories for this category
//and if the queue is empty, we are done
if (queue.length == 0) {
done(null, fullCats);
}
}
}
/**
* start here for getting sub categories, provide categoryId from which to start
*/
Category.getAllSubCategories = function(categoryId, cb) {
Category.findById(categoryId, function(err, cat) {
if (err) {
return cb(err);
}
var fullCats = []; //this variable holds all collected IDs of categories which are subcategories of the given category
var queue = []; //this array stores the IDs which have been queried; when a category is queried by parent, its ID is added;
//after returning from the callback, its ID is removed from here in order to clean the queue;
//we know that when the queue has become empty that all async calls have terminated
if (cat) {
//the category ID for which we are getting all subcategories needs to be included in final results!
fullCats.push(categoryId);
//add the first to the queue
queue.push(categoryId);
//now begin the recursive chain...
Category.getSubCategories(cat, queue, fullCats, catSearchCallback, function(err, allCats) {
if (err) {
cb(err);
}
//...and when finished, terminate the complete call with the callback
cb(null, allCats);
});
} else {
logger.info("Category.getAllSubCategories: category not found");
return categoryId;
}
});
}
我没有数据库的工作,而不是完整的代码为好,例如我不知道Category.find
是如何工作的。但随着人们在评论中说,这是绝对可以使用Promise一个代码,而我们在它,为什么不async / await。我无法运行此代码自己,所以考虑较完整的代码更蓝图/算法。所以,在这里不用什么。
与异步重写代码/等待:
Category.findAsync = function(condition){
return new Promise((resolve, reject) => Category.find(
condition,
(err, result) => err ? reject(err) : resolve(result)
))
}
Category.getSubCategories = async function(parentId){
return [parentId].concat(Promise.all(
(await Category.findAsync({where: {parentId}})).map(
subCategory => Category.getSubCategories(subCategory.id)
)
))
}
运行:
(async()=>{
const categoryId = 12345
console.log(await Category.getSubCategories(categoryId))
})()
因为,我不知道该怎么find
工作,我已经写了一个包装围绕它叫做findAsync
没有,将其转换回调承诺。
*回答结束*
PS:号召所有等待/无极专家编辑这个答案,并帮助OP达成有效的解决方案。