我有一个分层系统。类别具有父 ID(如果是根类别,则为 0)。
类别数据库中的示例行:
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达成可行的解决方案。