获取使用ES7 async/await的Knex.js事务

Nik*_*des 17 javascript async-await knex.js

我正在尝试将ES7的async/await与knex.js事务相结合.

虽然我可以轻松使用非事务性代码,但我正在努力使用上述异步/等待结构使事务正常工作.

我正在使用此模块来模拟async/await

这是我现在拥有的:

非交易版本:

工作正常,但不是交易

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);
  }
}));
Run Code Online (Sandbox Code Playgroud)

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));
  });
}
Run Code Online (Sandbox Code Playgroud)

尝试使其成为事务性的

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
  }
}),
Run Code Online (Sandbox Code Playgroud)

似乎await(knex.transaction())返回此错误:

[TypeError: container is not a function]

sf7*_*f77 27

我无法在任何地方找到一个可靠的答案(使用回滚和提交)所以这是我的解决方案.

首先,你需要"Promisify"这个knex.transaction功能.有这样的库,但是为了一个简单的例子,我做了这个:

const promisify = (fn) => new Promise((resolve, reject) => fn(resolve));
Run Code Online (Sandbox Code Playgroud)

此示例创建一个博客帖子和一个评论,如果两者都出错,则回滚两者.

const trx = await promisify(db.transaction);

try {
  const postId = await trx('blog_posts')
  .insert({ title, body })
  .returning('id'); // returns an array of ids

  const commentId = await trx('comments')
  .insert({ post_id: postId[0], message })
  .returning('id'); 

  await trx.commit();
} catch (e) {
  await trx.rollback();
}
Run Code Online (Sandbox Code Playgroud)

  • 我不得不使用`fn(resolve).catch(reject)`进行promisifying,否则我会在回滚时得到一个无法捕获的未处理的拒绝错误. (7认同)
  • 惊人的图案!在过去的 24 小时里,我一直在努力让交易在 knex 中工作,而您的代码使它变得如此简单。我在 TypeScript 中发布了我的解决方案。您是如何确定如何对交易进行 Promise 的? (2认同)
  • 您能否更新您的答案以与当前版本的 knex 兼容?请参阅/sf/answers/3929162261/ (2认同)

M. *_*put 14

这是一种在 async/await 中写入事务的方法。

它对于 MySQL 来说工作得很好。

const trx = await db.transaction();
try {
    const catIds = await trx('catalogues').insert({name: 'Old Books'});
    const bookIds = await trx('books').insert({catId: catIds[0], title: 'Canterbury Tales' });
    await trx.commit();
} catch (error) {
    await trx.rollback(error);
}
Run Code Online (Sandbox Code Playgroud)


Lan*_*ard 10

Async/await基于promises,所以看起来你只需要包装所有knex方法来返回"promise compatible"对象.

下面介绍如何将任意函数转换为使用promises,以便它们可以使用async/await:

试图了解promisification如何与BlueBird一起使用

基本上你想要这样做:

var transaction = knex.transaction;
knex.transaction = function(callback){ return knex.transaction(callback); }
Run Code Online (Sandbox Code Playgroud)

这是因为"async/await需要一个具有单个回调参数的函数或一个promise",而knex.transaction看起来像这样:

function transaction(container, config) {
  return client.transaction(container, config);
}
Run Code Online (Sandbox Code Playgroud)

或者,您可以创建一个新async函数并像这样使用它:

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
  }
})
Run Code Online (Sandbox Code Playgroud)

这也许有用:Knex Transaction with Promises

(另请注意,我不熟悉knex的API,因此不确定传递给params的内容knex.transaction,上面的内容只是例如).


Pet*_*ter 6

对于那些在 2019 年到来的人。

在我将 Knex 更新到 0.16.5 版之后。由于 Knextransaction功能的变化,sf77 的答案不再起作用:

transaction(container, config) {
  const trx = this.client.transaction(container, config);
  trx.userParams = this.userParams;
  return trx;
}
Run Code Online (Sandbox Code Playgroud)

解决方案

保留sf77的promisify功能:

const promisify = (fn) => new Promise((resolve, reject) => fn(resolve));
Run Code Online (Sandbox Code Playgroud)

更新 trx

const trx = await promisify(db.transaction);
Run Code Online (Sandbox Code Playgroud)

const trx =  await promisify(db.transaction.bind(db));
Run Code Online (Sandbox Code Playgroud)

  • 这对我有用。我希望SF77更新他们的答案。 (2认同)

nig*_*smk 5

我想我已经找到了一个更优雅的解决方案。

借用knex Transaction docs,我将他们的 promise 风格与对我有用的 async/await 风格进行对比。

承诺风格

var Promise = require('bluebird');

// Using trx as a transaction object:
knex.transaction(function(trx) {

  var books = [
    {title: 'Canterbury Tales'},
    {title: 'Moby Dick'},
    {title: 'Hamlet'}
  ];

  knex.insert({name: 'Old Books'}, 'id')
    .into('catalogues')
    .transacting(trx)
    .then(function(ids) {
      return Promise.map(books, function(book) {
        book.catalogue_id = ids[0];

        // Some validation could take place here.

        return knex.insert(book).into('books').transacting(trx);
      });
    })
    .then(trx.commit)
    .catch(trx.rollback);
})
.then(function(inserts) {
  console.log(inserts.length + ' new books saved.');
})
.catch(function(error) {
  // If we get here, that means that neither the 'Old Books' catalogues insert,
  // nor any of the books inserts will have taken place.
  console.error(error);
});
Run Code Online (Sandbox Code Playgroud)

异步/等待风格

var Promise = require('bluebird'); // import Promise.map()

// assuming knex.transaction() is being called within an async function
const inserts = await knex.transaction(async function(trx) {

  var books = [
    {title: 'Canterbury Tales'},
    {title: 'Moby Dick'},
    {title: 'Hamlet'}
  ];

  const ids = await knex.insert({name: 'Old Books'}, 'id')
    .into('catalogues')
    .transacting(trx);

  const inserts = await Promise.map(books, function(book) {
        book.catalogue_id = ids[0];

        // Some validation could take place here.

        return knex.insert(book).into('books').transacting(trx);
      });
    })
  await trx.commit(inserts); // whatever gets passed to trx.commit() is what the knex.transaction() promise resolves to.
})
Run Code Online (Sandbox Code Playgroud)

文档状态:

直接从事务处理函数抛出错误会自动回滚事务,与返回被拒绝的承诺相同。

似乎交易回调函数预计将不返回任何内容或 Promise。将回调声明为异步函数意味着它返回一个 Promise。

这种风格的一个优点是您不必手动调用回滚。返回被拒绝的 Promise 将自动触发回滚。

确保将您想在其他地方使用的任何结果传递给最终的 trx.commit() 调用。

我已经在我自己的工作中测试了这种模式,它按预期工作。