【问题标题】:How to prevent Knex.js from running a query object when returning it from an async function?从异步函数返回查询对象时,如何防止 Knex.js 运行查询对象?
【发布时间】:2019-07-04 20:53:35
【问题描述】:

我有一个 node.js 后端,它使用 Knex.js 从各种输入动态构建数据库查询。一些输入需要异步处理。我的问题是,我无法从异步函数(当然也可以在 Promise 解析函数中)返回 knex 查询对象,因为这会触发查询的执行。目前,我需要在将所有异步输入交给查询构建函数之前处理它们,但这确实限制了它们的可组合性。有没有办法阻止 Knex 在异步上下文中执行查询对象?

【问题讨论】:

    标签: javascript node.js knex.js


    【解决方案1】:

    您需要将构建器包装到函数或对象:

    async function returnsQueryBuilder() {
      return { builder : knex('mytable').where('foo', 'bar') };
    }
    
    const query = (await returnsQueryBuilder()).builder;
    

    因为异步函数实际上包装和解析返回的值/承诺/thenables 并且 knex 查询构建器是 thenablehttps://promisesaplus.com/ 第 1.2 章)它会自动重新解析。

    出于同样的原因,您也可以直接等待查询构建器从构建的查询中获取结果。如果 knex 查询生成器不是 thenable,这也不起作用:

    // returns all the rows of the table
    const result = await knex('table'); 
    

    所以正如我所说,唯一的选择是不直接返回查询构建器实例,而是将其包装到不是thenable 的东西。

    【讨论】:

    • 嘿米凯尔。谢谢您的回答。包装查询对象肯定会起作用。但这也会使 API 不那么优雅。我想知道是否可以重命名 Knex QueryBuilder 上的“then”函数并添加一个触发“then”并返回 promise 的执行函数。
    • 如果 API 使用某些实践,例如使用具有特定键的对象,具有构建器来传递查询构建器,比必须调用 promise() 之后才能触发该查询......无论如何,如果如果你想尝试让它更优雅,你可以编写自己的类来包装查询构建器实例并实现一些帮助器,使其具有比普通对象和魔法属性更好的 API。
    • 将此答案与 Github 问题交叉链接:github.com/tgriesser/knex/issues/2940#issuecomment-444401481
    【解决方案2】:

    感谢 Mikael Lepistö 的回答,我知道如何解决这个问题。正如他指出的那样,Knex 查询是thenables,因为它具有then 函数。 JavaScript await 关键字实际上调用了您提供给它的任何对象的 then 函数,无论是否承诺。因此,为了防止在 await(或 .then())上执行查询,您可以删除/重命名查询 then 函数。例如

    const getQuery = async () => {
      const qb = knex("users")
        .select("id")
        .limit(100);
      qb.promise = qb.then;
      qb.then = undefined;
      return qb;
    };
    
    const query = await getQuery();
    console.log(query.toString());
    console.log(await query.promise());
    

    更新,警告不要在家里尝试这个孩子s :)

    我觉得有必要指出 Mikael 在 cmets 中的有效批评。这是编写您自己的包装类的一种骇人且潜在危险的捷径,可能会使您的代码更难理解。但我也坚持我的评估,即在我的特定用例中正确键入 TypeScript 是一种有效且高效的解决方案。

    UPDATE2:现在不用弄乱原型了 :)。在实例上将 .then 设置为 undefined 就可以了。

    【讨论】:

    • 从查询构建器重命名.then 函数是一个非常糟糕的想法,如果您使用其他希望将 knex 查询构建器实例传递给它们的库,则可能会中断。如果没有 then 方法,也无法保证查询构建器继续正常工作。 Monkeypatching /破坏库内部通常是坏事,在这种情况下,这是完全没有必要的,甚至会使您的代码更难被使用该库的任何人阅读。这可能适用于您的特定用例,但不应被宣传为一种好的方法。
    • ...猴子修补整个库也是可能的,但更糟糕的是。
    • 总的来说我同意你的评价,特别是关于猴子补丁。更改查询实例的“then”方法似乎是一个合理的风险。此外,我正在将项目从 Flow 的旧版本(Knex 输入严重损坏)切换到 TypeScript,并将正确输入派生的 Knex 变体。这应该可以节省足够的空间来使用,而无需包装类。但总的来说,我同意对于特定用例来说这是一种方便有效的破解方法,但不是通用解决方案。这种方法有一个明确的令人讨厌的因素:)
    • Mikael,您在不破坏原型的情况下感觉如何? :)
    • 也好不到哪里去,它仍然无效(猴子补丁)knex 查询构建器,它可能有效,也可能无效。可能仍然可以在您的项目中正常工作:)
    【解决方案3】:

    对于我的需要,最重要的是提取 where 子句,因为这通常是复杂的部分,并且在多个查询之间共享。令人高兴的是,使用 Knex 很容易做到这一点。

    例如,如果您有这个查询,并且 where 函数是要与其他查询共享的内容。

    const data = await knex("mytable")
       .where(builder => {
          builder.whereNull("mytable.deleted_at")
    
          if (something) {
             builder.where("something", 42)
          }
       })
       .select("*")
    

    你可以重构为:

    const makeWhereClause = ({ something }) => builder => {
       builder.whereNull("mytable.deleted_at")
       if (something) {
          builder.where("something", 42)
       }
    }
    
    const data = await knex("mytable")
       .where(makeWhereClause({something})
       .select("*")
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-07-18
      • 2020-06-28
      • 2021-07-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多