使用 ES7 async/await 获取 Knex.js 个事务
Get Knex.js transactions working with ES7 async/await
我正在尝试将 ES7 的 async/await 与 knex.js transactions.
结合起来
虽然我可以轻松地使用非事务代码,但我正在努力使用上述 async/await 结构使事务正常工作。
我正在使用 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())
returns这个错误:
[TypeError: container is not a function]
Async/await 是基于承诺的,因此看起来您只需要将所有 knex 方法包装到 return "promise compatible" 对象。
这里描述了如何将任意函数转换为与 promise 一起工作,以便它们可以与 async/await 一起工作:
Trying to understand how promisification works with BlueBird
基本上你想这样做:
var transaction = knex.transaction;
knex.transaction = function(callback){ return knex.transaction(callback); }
这是因为 "async/await requires the either a function with a single callback argument, or a promise",而 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
的参数是什么,以上只是举例)。
我在任何地方都找不到可靠的答案(包括回滚和提交)所以这是我的解决方案。
首先你需要"Promisify" knex.transaction
函数。有用于此的库,但为了快速举例,我这样做了:
const promisify = (fn) => new Promise((resolve, reject) => fn(resolve));
此示例创建了一个博客 post 和一条评论,如果两者都出错,则回滚。
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();
}
除了 sf77 的出色回答之外,我在 TypeScript 中实现了这个模式,用于添加一个新用户,您需要在 1 个事务中执行以下操作:
- 正在 USER 中创建用户记录 table
- 正在 LOGIN 中创建登录记录 table
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)
}
}
我想我找到了一个更优雅的解决问题的方法。
借鉴 knex Transaction docs,我会将他们的承诺风格与对我有用的 async/await-style 进行对比。
承诺风格
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);
});
async/await风格
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.
})
文档状态:
Throwing an error directly from the transaction handler function automatically rolls back the transaction, same as returning a rejected promise.
似乎交易回调函数预计 return 要么什么都不做,要么是 Promise。将回调声明为异步函数意味着它 return 是一个 Promise。
这种风格的一个优点是您不必手动调用回滚。返回被拒绝的 Promise 将自动触发回滚。
确保将要在别处使用的任何结果传递给最终的 trx.commit() 调用。
我已经在自己的工作中测试了这个模式,它按预期工作。
致 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));
这是一种在异步/等待中编写事务的方法。
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);
}
我正在尝试将 ES7 的 async/await 与 knex.js transactions.
结合起来虽然我可以轻松地使用非事务代码,但我正在努力使用上述 async/await 结构使事务正常工作。
我正在使用 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())
returns这个错误:
[TypeError: container is not a function]
Async/await 是基于承诺的,因此看起来您只需要将所有 knex 方法包装到 return "promise compatible" 对象。
这里描述了如何将任意函数转换为与 promise 一起工作,以便它们可以与 async/await 一起工作:
Trying to understand how promisification works with BlueBird
基本上你想这样做:
var transaction = knex.transaction;
knex.transaction = function(callback){ return knex.transaction(callback); }
这是因为 "async/await requires the either a function with a single callback argument, or a promise",而 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
的参数是什么,以上只是举例)。
我在任何地方都找不到可靠的答案(包括回滚和提交)所以这是我的解决方案。
首先你需要"Promisify" knex.transaction
函数。有用于此的库,但为了快速举例,我这样做了:
const promisify = (fn) => new Promise((resolve, reject) => fn(resolve));
此示例创建了一个博客 post 和一条评论,如果两者都出错,则回滚。
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();
}
除了 sf77 的出色回答之外,我在 TypeScript 中实现了这个模式,用于添加一个新用户,您需要在 1 个事务中执行以下操作:
- 正在 USER 中创建用户记录 table
- 正在 LOGIN 中创建登录记录 table
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)
}
}
我想我找到了一个更优雅的解决问题的方法。
借鉴 knex Transaction docs,我会将他们的承诺风格与对我有用的 async/await-style 进行对比。
承诺风格
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);
});
async/await风格
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.
})
文档状态:
Throwing an error directly from the transaction handler function automatically rolls back the transaction, same as returning a rejected promise.
似乎交易回调函数预计 return 要么什么都不做,要么是 Promise。将回调声明为异步函数意味着它 return 是一个 Promise。
这种风格的一个优点是您不必手动调用回滚。返回被拒绝的 Promise 将自动触发回滚。
确保将要在别处使用的任何结果传递给最终的 trx.commit() 调用。
我已经在自己的工作中测试了这个模式,它按预期工作。
致 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));
这是一种在异步/等待中编写事务的方法。
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);
}