Search code examples
javascriptasync-awaitknex.js

Get Knex.js transactions working with ES7 async/await


I'm trying to couple ES7's async/await with knex.js transactions.

Author Note:
Warning I posted this question at a time when async/await was still a proposal-level feature. The async/await I'm talking about here is similar but not identical to what you today know as async/await.

Although I can easily play around with non-transactional code, I've found it tricky to make it play nice with knex.js transactions.

I'm using this module to simulate async/await

Here's what I currently have:

non-transactional

works fine but is not transactional

app.js

// assume `db` is a knex instance

app.post("/user", async((req, res) => {
  const data = {
   idUser: 1,
   name: "FooBar"
  }

  try {
    const result = await(user.insert(db, data));
    res.json(result);
  } catch (err) {
    res.status(500).json(err);
  }
}));

user.js

insert: async (function(db, data) {
  // there's no need for this extra call but I'm including it
  // to see example of deeper call stacks if this is answered

  const idUser =  await(this.insertData(db, data));
  return {
    idUser: idUser
  }
}),

insertData: async(function(db, data) {
  // if any of the following 2 fails I should be rolling back

  const id = await(this.setId(db, idCustomer, data));
  const idCustomer = await(this.setData(db, id, data));

  return {
    idCustomer: idCustomer
  }
}),

// DB Functions (wrapped in Promises)

setId: function(db, data) {
  return new Promise(function (resolve, reject) {
    db.insert(data)
    .into("ids")
    .then((result) => resolve(result)
    .catch((err) => reject(err));
  });
},

setData: function(db, id, data) {
  data.id = id;

  return new Promise(function (resolve, reject) {
    db.insert(data)
    .into("customers")
    .then((result) => resolve(result)
    .catch((err) => reject(err));
  });
}

transactional:attempt 1

user.js

// Start transaction from this call

insert: async (function(db, data) {
 const trx = await(knex.transaction());
 const idCustomer =  await(user.insertData(trx, data));

 return {
    idCustomer: idCustomer
  }
}),

it seems that await(knex.transaction()) returns this error:

[TypeError: container is not a function]


Solution

  • Async/await is based around promises, so it looks like you'd just need to wrap all the knex methods to return "promise compatible" objects.

    Here is a description on how you can convert arbitrary functions to work with promises, so they can work with async/await:

    Trying to understand how promisification works with BlueBird

    Essentially you want to do this:

    var transaction = knex.transaction;
    knex.transaction = function(callback){ return knex.transaction(callback); }
    

    This is because "async/await requires the either a function with a single callback argument, or a promise", whereas knex.transaction looks like this:

    function transaction(container, config) {
      return client.transaction(container, config);
    }
    

    Alternatively, you can create a new async function and use it like this:

    async function transaction() {
      return new Promise(function(resolve, reject){
        knex.transaction(function(error, result){
          if (error) {
            reject(error);
          } else {
            resolve(result);
          }
        });
      });
    }
    
    // Start transaction from this call
    
    insert: async (function(db, data) {
     const trx = await(transaction());
     const idCustomer =  await(person.insertData(trx, authUser, data));
    
     return {
        idCustomer: idCustomer
      }
    })
    

    This may be useful too: Knex Transaction with Promises

    (Also note, I'm not familiar with knex's API, so not sure what the params are passed to knex.transaction, the above ones are just for example).