You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 09:53:12