admin管理员组

文章数量:1022949

I have a node.js backend that constructs DB queries dynamically from various inputs using Knex.js. Some of the inputs need to be processed asynchronously. My problem is, that I can't return a knex query object from an async function (or of course in a Promise resolve function) because this triggers the execution of the query. Currently I need to process all my async inputs before handing them to the query building functions but that really limits their posability. Is there a way to prevent Knex from executing a query object in an async context?

I have a node.js backend that constructs DB queries dynamically from various inputs using Knex.js. Some of the inputs need to be processed asynchronously. My problem is, that I can't return a knex query object from an async function (or of course in a Promise resolve function) because this triggers the execution of the query. Currently I need to process all my async inputs before handing them to the query building functions but that really limits their posability. Is there a way to prevent Knex from executing a query object in an async context?

Share Improve this question asked Jul 4, 2019 at 20:53 d-vined-vine 5,5473 gold badges30 silver badges26 bronze badges
Add a ment  | 

3 Answers 3

Reset to default 2

You need to wrap builder to a function or to an object:

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

const query = (await returnsQueryBuilder()).builder;

Because async functions actually wraps and resolves returned values/promises/thenables and knex query builder is a thenable (https://promisesaplus./ chapter 1.2) it gets reasolved automatically.

For the same reason you can also directly await query builder to get result from the built query. Without knex query builder being thenable this would not work either:

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

So as I said the only option is to not return query builder instance directly, but wrap it to something that is not thenable.

Thanks to Mikael Lepistö's answer I got an idea how to work around this. As he pointed out Knex queries are thenables by virtue of having a then function. The JavaScript await keyword actually calls the then function of any object you present to it, regardless if promise or not. So in order to prevent query execution on await (or .then()) you can remove/rename the queries then function. E.g.

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());

UPDATE, WARNING: Don't try this at home kids :)

I feel obliged to point to Mikael's valid criticism in the ments. This a hacky and potential dangerous shortcut to writing your own wrapper class and might make your code harder to understand. But I also stand by my assessment that with proper TypeScript typing in my specific use case it's a valid and efficient solution.

UPDATE2: Now without messing up the prototype :). Setting .then to undefined on the instance works just fine.

For my needs, the most important is to extract the where clause, because this is often the plicated part and it's shared between several queries. Happily, this is easy to do with Knex.

For example, if you have this query, and the where function is the stuff to share with other queries.

const data = await knex("mytable")
   .where(builder => {
      builder.whereNull("mytable.deleted_at")

      if (something) {
         builder.where("something", 42)
      }
   })
   .select("*")

You can refactor as:

const makeWhereClause = ({ something }) => builder => {
   builder.whereNull("mytable.deleted_at")
   if (something) {
      builder.where("something", 42)
   }
}

const data = await knex("mytable")
   .where(makeWhereClause({something})
   .select("*")

I have a node.js backend that constructs DB queries dynamically from various inputs using Knex.js. Some of the inputs need to be processed asynchronously. My problem is, that I can't return a knex query object from an async function (or of course in a Promise resolve function) because this triggers the execution of the query. Currently I need to process all my async inputs before handing them to the query building functions but that really limits their posability. Is there a way to prevent Knex from executing a query object in an async context?

I have a node.js backend that constructs DB queries dynamically from various inputs using Knex.js. Some of the inputs need to be processed asynchronously. My problem is, that I can't return a knex query object from an async function (or of course in a Promise resolve function) because this triggers the execution of the query. Currently I need to process all my async inputs before handing them to the query building functions but that really limits their posability. Is there a way to prevent Knex from executing a query object in an async context?

Share Improve this question asked Jul 4, 2019 at 20:53 d-vined-vine 5,5473 gold badges30 silver badges26 bronze badges
Add a ment  | 

3 Answers 3

Reset to default 2

You need to wrap builder to a function or to an object:

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

const query = (await returnsQueryBuilder()).builder;

Because async functions actually wraps and resolves returned values/promises/thenables and knex query builder is a thenable (https://promisesaplus./ chapter 1.2) it gets reasolved automatically.

For the same reason you can also directly await query builder to get result from the built query. Without knex query builder being thenable this would not work either:

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

So as I said the only option is to not return query builder instance directly, but wrap it to something that is not thenable.

Thanks to Mikael Lepistö's answer I got an idea how to work around this. As he pointed out Knex queries are thenables by virtue of having a then function. The JavaScript await keyword actually calls the then function of any object you present to it, regardless if promise or not. So in order to prevent query execution on await (or .then()) you can remove/rename the queries then function. E.g.

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());

UPDATE, WARNING: Don't try this at home kids :)

I feel obliged to point to Mikael's valid criticism in the ments. This a hacky and potential dangerous shortcut to writing your own wrapper class and might make your code harder to understand. But I also stand by my assessment that with proper TypeScript typing in my specific use case it's a valid and efficient solution.

UPDATE2: Now without messing up the prototype :). Setting .then to undefined on the instance works just fine.

For my needs, the most important is to extract the where clause, because this is often the plicated part and it's shared between several queries. Happily, this is easy to do with Knex.

For example, if you have this query, and the where function is the stuff to share with other queries.

const data = await knex("mytable")
   .where(builder => {
      builder.whereNull("mytable.deleted_at")

      if (something) {
         builder.where("something", 42)
      }
   })
   .select("*")

You can refactor as:

const makeWhereClause = ({ something }) => builder => {
   builder.whereNull("mytable.deleted_at")
   if (something) {
      builder.where("something", 42)
   }
}

const data = await knex("mytable")
   .where(makeWhereClause({something})
   .select("*")

本文标签: