1

I'm trying to understand how the await keyword is being used in KNEX. Take the following example:

knex.schema.createTable( ... );

this will return this which is an instance of SchemaBuilder. It will not execute the create table query in the database. However if I stick and await in front of it.

await knex.schema.createTable( ... );

This will now execute the create query in the database.

My understanding is that await is used to wait for a promise resolution but in this case it feels like something else is going on because not awaiting the function doesn't return a promise.

How does this work?

xom9ikk
  • 2,159
  • 5
  • 20
  • 27
GeraldHost
  • 13
  • 1
  • 2
  • This was my understanding but if I don't `await` the function call it definitely doesn't create the table in the database. Compared with if I do use `await` it will execute the query and the table will appear in the database. – GeraldHost Dec 16 '20 at 08:53
  • 1
    @T.J.Crowder The former expression just creates a SchemaBuilder, the latter expression calls the SchemaBuilder's `.then()` method. – Bergi Dec 16 '20 at 13:19
  • @Bergi - Why, *why* didn't I think of a custom thenable?? – T.J. Crowder Dec 16 '20 at 13:23
  • @Bergi you are right! I didn't realise you could set up a then prototype that would be called when you use `await`. `await fn()` is just simply syntax sugar for `fn().then()`. Thank you! – GeraldHost Dec 21 '20 at 14:00

1 Answers1

3

If you want to know how knex only makes requests when you write await in front of the construct, then here.

Under the hood, knex uses a pattern that returns an object with a then field.

const asyncFunction = (delay) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      return resolve(delay);
    }, delay);
  })
}

const builder = (delay) => {
  return {
    then: async (resolve) => {
      const result = await asyncFunction(delay);
      return resolve(result);
    }
  }
}

const main = async () => {
  const array = [];
  for(let i=0; i<10; i++) {
    array.push(builder(i));
  }
  console.log('array', array);
  console.log('array[0]', array[0]);
  console.log('await array[0]', await array[0]);
  console.log('Promise.all for array', await Promise.all(array));
}

main();

The result of this execution will be the following output to the console

array [
  { then: [AsyncFunction: then] },
  { then: [AsyncFunction: then] },
  { then: [AsyncFunction: then] },
  { then: [AsyncFunction: then] },
  { then: [AsyncFunction: then] },
  { then: [AsyncFunction: then] },
  { then: [AsyncFunction: then] },
  { then: [AsyncFunction: then] },
  { then: [AsyncFunction: then] },
  { then: [AsyncFunction: then] }
]
array[0] { then: [AsyncFunction: then] }
await array[0] 0
Promise.all for array [
  0, 1, 2, 3, 4,
  5, 6, 7, 8, 9
]

As you can see, the code inside the then function will not be called until the await keyword or other ways of waiting for Promise are used.

xom9ikk
  • 2,159
  • 5
  • 20
  • 27
  • 1
    Don't forget the second parameter to `then`. Also I would recommend not to make `then` an `async function` to avoid weird handling of rejections. Just make `{ then(...args) { return asyncFunction().then(...args); } }`. – Bergi Dec 16 '20 at 13:23