使用Node.js与MySQL结合Promise生成树形结构数据
把MySQL N级分类转成结构化嵌套数组的Promise递归实现
嘿,我看你正在尝试用Promise递归处理MySQL的n级分类数据(id/title/parent_id结构),转成嵌套的结构化数组对吧?原代码看起来没写完,而且还有几个容易踩的坑——比如数据库连接没释放、Promise没正确resolve、异步回调嵌套混乱这些。我帮你完善并优化了完整实现,一起来看看:
const recursiveFindCat = (parentId) => { return new Promise((resolve, reject) => { // 从连接池拿连接 connections.getConnection((err, conn) => { if (err) { return reject(err); } // 划重点:用参数化查询防SQL注入!别直接拼字符串 conn.query( 'SELECT id, title, parent_id FROM widgets WHERE parent_id = ?', [parentId], (error, results) => { // 不管查成啥样,用完连接一定要释放,不然连接池会炸 conn.release(); if (error) { return reject(error); } // 没子节点就直接返回空数组 if (results.length === 0) { return resolve([]); } // 并行处理所有子节点的递归,等全部完成再返回结构 Promise.all( results.map(async (item) => { // 递归获取当前节点的所有子节点 const children = await recursiveFindCat(item.id); // 把子节点挂到当前节点上,返回结构化对象 return { ...item, children: children }; }) ) .then(structuredArray => resolve(structuredArray)) .catch(err => reject(err)); } ); }); }); }; // 调用示例:假设根节点的parent_id是0,根据你的数据库实际情况调整 recursiveFindCat(0) .then(categories => console.log(JSON.stringify(categories, null, 2))) .catch(err => console.error('获取分类结构失败:', err));
几个关键优化点说一下:
- 防SQL注入:绝对不要直接把参数拼到SQL里!用
?占位符加参数数组,这是基本的安全操作。 - 连接释放:查询完不管成功失败都要调用
conn.release(),不然连接池里的连接会被耗尽,后续请求就挂了。 - Promise异步流程:用
Promise.all等待所有子节点的递归Promise完成,确保整个异步流程是顺序正确的,不会出现返回空结构的情况。 - 嵌套结构构建:每个节点都会带上
children属性,自动嵌套所有子节点,完美生成n级结构。
额外给个性能优化方案:
如果你的分类数据量比较大,递归查数据库会发很多请求,性能不太好。可以一次性把所有分类查出来,然后在内存里构建嵌套结构,只需要一次数据库查询,效率高很多:
const buildNestedCategories = () => { return new Promise((resolve, reject) => { connections.getConnection((err, conn) => { if (err) return reject(err); conn.query('SELECT id, title, parent_id FROM widgets', (error, results) => { conn.release(); if (error) return reject(error); // 先把所有节点用id做映射,方便快速查找父节点 const nodeMap = {}; const rootNodes = []; results.forEach(node => { nodeMap[node.id] = { ...node, children: [] }; }); // 遍历所有节点,把它们挂到对应的父节点下 results.forEach(node => { if (node.parent_id === 0) { // 这里的根节点条件根据你的数据库调整 rootNodes.push(nodeMap[node.id]); } else { // 确保父节点存在(避免脏数据导致的错误) if (nodeMap[node.parent_id]) { nodeMap[node.parent_id].children.push(nodeMap[node.id]); } } }); resolve(rootNodes); }); }); }); }; // 调用示例 buildNestedCategories() .then(categories => console.log(JSON.stringify(categories, null, 2))) .catch(err => console.error('构建嵌套分类失败:', err));
这种方式适合数据量较大的场景,一次性拉取所有数据再处理,比递归查数据库高效多了。
内容的提问来源于stack exchange,提问作者André Eccel
相关产品推荐
相关产品推荐

