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

时间:2016-11-14 01:22:07

标签: javascript async-await knex.js

我试图将ES7的async / await与knex.js transactions.

结合起来

虽然我可以轻松使用非事务性代码,但我很难使用前面提到的异步/等待结构来使事务正常工作。

我正在使用this module to simulate 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);
  }
}));

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

尝试使其成为交易

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
  }
}),

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

[TypeError: container is not a function]

6 个答案:

答案 0 :(得分:25)

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

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

const promisify = (fn) => new Promise((resolve, reject) => fn(resolve));

此示例创建博客帖子和评论,如果其中任何一个出现错误,则回滚两者。

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

答案 1 :(得分:11)

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

以下是关于如何将任意函数转换为使用promises的描述,因此它们可以使用async / await:

Trying to understand how promisification works with BlueBird

基本上你想要这样做:

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

这是因为“async / await要求具有单个回调参数的函数或承诺”,而knex.transaction看起来像这样:

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

或者,您可以创建一个新的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
  }
})

这也可能有用:Knex Transaction with Promises

(另请注意,我不熟悉knex的API,因此不确定传递给knex.transaction的参数是什么,以上只是例如)。

答案 2 :(得分:3)

除了sf77的出色答案外,我还在TypeScript中实现了此模式,以便在需要在1个事务中执行以下操作的情况下添加新用户:

  1. 在USER表中创建用户记录
  2. 在LOGIN表中创建登录记录

public async addUser(user: User, hash: string): Promise<User> {

	//transform knex transaction such that can be used with async-await
	const promisify = (fn: any) => new Promise((resolve, reject) => fn(resolve));
	const trx: knex.Transaction  = <knex.Transaction> await promisify(db.transaction);

	try {
		let users: User [] = await trx
			.insert({
				name: user.name,
				email: user.email,
				joined: new Date()})
			.into(config.DB_TABLE_USER)
			.returning("*")

		await trx
			.insert({
				email: user.email,
				hash
			}).into(config.DB_TABLE_LOGIN)
			.returning("email")
		await trx.commit();
		return Promise.resolve(users[0]);
	}
	catch(error) { 
		await trx.rollback;
		return Promise.reject("Error adding user: " + error) 
	}
}

答案 3 :(得分:3)

我认为我找到了解决该问题的更优雅的方法。

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

承诺风格

var mongoose = require("mongoose");
var camp = require("./models/camp");
var Comment = require("./models/comment");

var data = [
    {
        name: "Cloud's Rest", 
        image: // a link goes here
    },
    {
        name: "Desert Mesa", 
        image: //
    },
    {
        name: "Canyon Floor", 
        image: //
    }
];

function seedDB(){
   //Remove all campgrounds that existed in the database & create new campgrounds with data array.

   camp.remove({}, function(err){
        if(err){
            console.log(err);
        }
        console.log("removed campgrounds!");
         //add a few campgrounds
        data.forEach(function(seed){
            camp.create(seed, function(err, camp){
                if(err){
                    console.log(err)
                } else {
                    console.log("added a campground");
                    //create a comment
                    Comment.create(
                        {
                            text: "This place is great, but I wish there was internet",
                            author: "Homer"
                        }, function(err, comment){
                            if(err){
                                console.log(err);
                            } else {
                                camp.comments.push(comment);
                                camp.save();
                                console.log("Created new comment");
                            }
                        });
                }
            });
        });
    }); 
    //add a few comments
}


module.exports = seedDB;

异步/等待样式

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

文档状态:

  

直接从事务处理函数中引发错误会自动回滚事务,就像返回被拒绝的诺言一样。

似乎事务回调函数应该不返回任何内容或返回Promise。将回调声明为异步函数意味着它返回一个Promise。

此样式的一个优点是您不必手动调用回滚。返回被拒绝的Promise将自动触发回滚。

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

我已经在自己的工作中测试了这种模式,并且可以正常工作。

答案 4 :(得分:1)

这是一种在异步/等待状态下写入事务的方法。

它在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);
}

答案 5 :(得分:0)

针对2019年的来访者。

在我将Knex更新到版本0.16.5之后。由于Knex的transaction函数的更改,sf77的答案不再起作用:

transaction(container, config) {
  const trx = this.client.transaction(container, config);
  trx.userParams = this.userParams;
  return trx;
}

解决方案

保留sf77的promisify函数:

const promisify = (fn) => new Promise((resolve, reject) => fn(resolve));

更新trx

来自

const trx = await promisify(db.transaction);

const trx =  await promisify(db.transaction.bind(db));