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

如何用Knex实现GetOrCreate函数及解决异步查询循环问题

嘿,刚接触 Node.js、Promise 和 Knex 第一天就上手做这么实用的功能,已经超棒啦!我来一步步帮你理清思路,解决这些问题~

一、关于 first() 的疑问:必须加!

你提到两个查询最多返回 0 或 1 行,用 first() 绝对是最佳选择——它会自动给 SQL 加上 LIMIT 1,并且返回的 Promise 会直接解析为单个对象(比如 {id: 123})或者 undefined,比处理数组结果要简洁太多。修正后的查询应该是:

knex("ingredients").select('id').where('name', my_param).first()
knex("synonyms").select('word_id').where('name', my_param).first()

二、实现 ingredientGetOrCreate 函数

用 async/await 来写这个函数会比嵌套 .then() 直观很多,完全符合你“先查后插”的需求:

async function ingredientGetOrCreate(my_param) {
  // 并行查询两张表,比串行查效率更高
  const [ingredientRes, synonymRes] = await Promise.all([
    knex("ingredients").select('id').where('name', my_param).first(),
    knex("synonyms").select('word_id').where('name', my_param).first()
  ]);

  // 优先返回已有结果
  if (ingredientRes) return ingredientRes.id;
  if (synonymRes) return synonymRes.word_id;

  // 两张表都没数据,插入新食材并返回ID
  // 注意:returning('id') 是 Knex 获取插入后ID的标准写法,适配多数数据库
  const [newIngredientId] = await knex("ingredients")
    .insert({ name: my_param.trim() }) // 建议trim避免空格导致重复创建
    .returning('id');
  
  return newIngredientId;
}

这里用 Promise.all 同时发起两个查询,比先查一张再查另一张节省一半时间,非常适合你的场景。

三、解决循环中的异步陷阱

你原来的代码有两个核心问题:没正确等待异步操作完成,也没给关联表传入产品ID。修正后的版本如下:

knex("products")
  .select("id", "name", "desc") // 别忘了选desc字段!
  .then(async (products) => {
    // 遍历每个产品,用for...of配合await确保顺序处理(也可以用Promise.all并行)
    for (const product of products) {
      // 分割描述并去除空格,避免空字符串或重复值
      const ingredients = product.desc.split(",").map(item => item.trim()).filter(Boolean);
      
      // 并行处理当前产品的所有食材
      await Promise.all(ingredients.map(async (ingredientName) => {
        // 等待食材ID获取/创建完成
        const ingredientId = await ingredientGetOrCreate(ingredientName);
        // 插入关联表,必须等待这个操作完成!
        await knex('ingredients_products').insert({
          id_of_ingredient: ingredientId,
          id_of_product: product.id // 关键:关联当前产品的ID
        });
      }));
    }
    console.log('所有产品关联完成!');
  })
  .catch(err => {
    // 一定要捕获错误,不然异步出错会静默消失
    console.error('处理失败:', err);
  });

几个新手必注意点:

  1. 必须返回/等待Promise:Promise.all 需要接收一个 Promise 数组,所以 .map() 里的函数要加 async,并且每个异步操作都要用 await。
  2. 关联产品ID:你的原代码漏了给 ingredients_products 传入产品ID,这会导致关联表数据无效,一定要补上。
  3. 处理空字符串:用 filter(Boolean) 过滤分割后可能出现的空值,避免不必要的数据库操作。
  4. 错误捕获:异步代码的错误一定要用 .catch() 捕获,不然程序出错了你根本找不到原因。

内容的提问来源于stack exchange,提问作者renathy

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 03:23:50