0

Is it possible to implement an async version of "modify"?

Modify method does not look to support async callback.

I have tried overriding the "then" method of the query builder instance,

running my async code before the real "then" method. But the resulting object looks like to not be fully compatible with knex (for example clone method seems to not work)

Fabiano Taioli
  • 5,270
  • 1
  • 35
  • 49
  • The `modify` method is used to synchronously build a query, nothing else. It is unclear what you mean by "async version", or what you are attempting to achieve. – Bergi Apr 07 '23 at 13:19

1 Answers1

0

Knex's modify function is meant for synchronous composition:

Allows encapsulating and re-using query snippets and common behaviors as functions. The callback function should receive the query builder as its first argument, followed by the rest of the (optional) parameters passed to modify.

Making it generally asynchronous has a practical problem and also a semantic problem. For the practical problem, as in your example, the return value of callback.apply is not observed or returned: If the function is async or it otherwise returns a Promise, there's no way to signal to you that it's done. The semantic problem is that ordering matters (for example, when passing multiple orderBy clauses). Knex's syntax is full of synchronous operations, not Promises; the assumption is that the query produced by Knex is available synchronously.

Furthermore, as a practical challenge in the language, it is difficult to return a Knex query builder as a result of a Promise because Knex builders have a Promise-like then function. Promise-resolving systems in JavaScript (including Promise.resolve and async function returns) will accept an object with a then function as a "thenable", or a foreign promise implementation, which the Promise system further unwraps. Consequently, returning a Knex promise builder is not possible without a wrapper Array or Object, or else JS will call then on the builder and execute the query.

All that said, there's nothing preventing you from creating your own modifyAsync function with the same semantics as modify, which waits for the passed asynchronous callback function and then returns a Promise that resolves to the wrapped modified query builder.

async function modifyAsync(queryBuilder, asyncCallback, ...args) {
  await asyncCallback.call(queryBuilder, queryBuilder, ...args);
  // Wrap in an array to prevent JS from calling queryBuilder.then.
  return [queryBuilder];
}

async function createQuery() {
  let builder = knex.table(yourTable)
    /* ... */;
  // Destructure using an array.
  [builder] = await modifyAsync(builder, yourAsyncModifyFunction, 'foo', 'bar');
  // Further modify the builder.
  builder.orderBy(/* ... */);
  // Return the builder, again through an array to avoid the async
  // function automatically calling `then`.
  return [builder];
}
Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251
  • I think your "createQuery()" function do not return a queryBuilder object but run the query and return a Promise that will resolve with the query result. [Tried to implement it extending knex but looks like a bit hacker y](https://gist.github.com/FbN/e0c8f14f95322473ca75c86ebf1b171) – Fabiano Taioli Apr 11 '23 at 09:15
  • @FabianoTaioli Oh right, it's hard to return a Promise to a Knex object because it has a `then` function; the Promises spec says it will keep unwrapping the value until the return doesn't have a `then`. You would need to wrap the partial query in an array or object to stop the unwrapping. I'll add code comments. – Jeff Bowman Apr 11 '23 at 15:05