如何使用Mongoose正确获取文档所有祖先?解决await语法错误
如何使用Mongoose正确获取树形结构文档的所有祖先
问题回顾
你采用MongoDB官方推荐的「祖先数组建模树形结构」方案存储分类数据,但在尝试获取某个文档的所有祖先时遇到了异步逻辑的问题:
- 最初用回调式的
model.findOne()循环查询,因为异步特性导致数组无法正确收集祖先节点; - 改用
await优化后,又触发了SyntaxError: Unexpected identifier错误。
先看你的示例数据:
db.categories.insert( { _id: "MongoDB", ancestors: [ "Books", "Programming", "Databases" ], parent: "Databases" } ) db.categories.insert( { _id: "dbm", ancestors: [ "Books", "Programming", "Databases" ], parent: "Databases" } ) db.categories.insert( { _id: "Databases", ancestors: [ "Books", "Programming" ], parent: "Programming" } ) db.categories.insert( { _id: "Languages", ancestors: [ "Books", "Programming" ], parent: "Programming" } ) db.categories.insert( { _id: "Programming", ancestors: [ "Books" ], parent: "Books" } ) db.categories.insert( { _id: "Books", ancestors: [ ], parent: null } )
错误原因拆解
- 回调式循环的问题:
model.findOne()是异步操作,循环会在所有回调执行前就完成迭代,导致ancestors数组无法按顺序收集数据,nextParent的更新也不会触发下一次查询。 - await语法错误的问题:
await关键字只能在标记为async的函数内部使用,你的代码外层没有包裹async函数,所以JavaScript引擎会抛出语法错误。
正确解决方案
方案1:用async/await重构循环(适合无预存ancestors的场景)
把查询逻辑放在async函数内,让循环按顺序等待每一次查询完成,确保数据正确收集:
// 封装成异步函数 async function fetchAncestors(parentId) { const ancestors = []; let nextParent = parentId; while (nextParent) { try { // 等待查询完成再继续 const parentCategory = await model.findOne({ _id: nextParent }); if (parentCategory) { ancestors.unshift(nextParent); // 更新为父节点的ID,继续循环 nextParent = parentCategory.parent; } else { // 找不到父节点,终止循环 nextParent = null; } } catch (err) { console.error('查询父节点失败:', err); throw err; // 可根据业务需求处理错误 } } return ancestors; } // 在你的请求处理中调用(注意请求处理函数也要标记为async) app.post('/get-ancestors', async (req, res) => { const { parent } = req.body; try { const ancestors = await fetchAncestors(parent); res.status(200).json({ ancestors }); } catch (err) { res.status(500).json({ error: 'Failed to fetch ancestors' }); } });
方案2:直接利用预存的ancestors数组(最优解)
既然你已经采用了MongoDB的祖先数组建模方案,每个文档的ancestors字段已经存储了所有祖先节点,直接查询这个字段就能一次性获取结果,不需要循环查询:
async function getAncestorsDirectly(categoryId) { // 只查询ancestors字段,减少数据传输 const category = await model.findOne( { _id: categoryId }, { ancestors: 1, _id: 0 } ); return category ? category.ancestors : []; } // 调用示例:获取"MongoDB"的所有祖先 const mongoDBAncestors = await getAncestorsDirectly("MongoDB"); // 返回结果:["Books", "Programming", "Databases"]
这个方案效率更高,只需要一次数据库请求,完全发挥了这种树形建模方案的优势。
总结
- 如果你的业务场景无法预存
ancestors数组,用async/await包裹循环是正确的解决方式,务必确保await在async函数内使用; - 若已经采用祖先数组建模,直接读取
ancestors字段是最优选择,避免不必要的多次数据库查询。
内容的提问来源于stack exchange,提问作者5ervant - techintel.github.io
相关产品推荐
相关产品推荐

