Node.js 7 и выше уже поддерживают синтаксис async/await. Как я должен использовать async/await с транзакциями sequelize?
Node.js 7 как использовать транзакцию selizeize с async/wait?
Ответ 1
let transaction;
try {
// get transaction
transaction = await sequelize.transaction();
// step 1
await Model.destroy({where: {id}, transaction});
// step 2
await Model.create({}, {transaction});
// step 3
await Model.update({}, {where: {id}, transaction });
// commit
await transaction.commit();
} catch (err) {
// Rollback transaction only if the transaction object is defined
if (transaction) await transaction.rollback();
}
Ответ 2
Принятый ответ - "неуправляемая транзакция", которая требует явного вызова commit
и rollback
. Для тех, кто хочет "управляемую транзакцию", это выглядит так:
try {
// Result is whatever you returned inside the transaction
let result = await sequelize.transaction( async (t) => {
// step 1
await Model.destroy({where: {id: id}, transaction: t});
// step 2
return await Model.create({}, {transaction: t});
});
// In this case, an instance of Model
console.log(result);
} catch (err) {
// Rollback transaction if any errors were encountered
console.log(err);
}
Чтобы выполнить откат, просто сгенерируйте ошибку внутри функции транзакции:
try {
// Result is whatever you returned inside the transaction
let result = await sequelize.transaction( async (t) => {
// step 1
await Model.destroy({where: {id:id}, transaction: t});
// Cause rollback
if( false ){
throw new Error('Rollback initiated');
}
// step 2
return await Model.create({}, {transaction: t});
});
// In this case, an instance of Model
console.log(result);
} catch (err) {
// Rollback transaction if any errors were encountered
console.log(err);
}
Если какой-либо код выдает ошибку внутри блока транзакции, откат автоматически срабатывает.
Ответ 3
В ответе пользователя 7403683 описывается асинхронный/ожидающий путь для неуправляемой транзакции (http://docs.sequelizejs.com/manual/tutorial/transactions.html#unmanaged-transaction-then-callback-).
Управляемая транзакция в стиле async/await может выглядеть следующим образом:
await sequelize.transaction( async t=>{
const user = User.create( { name: "Alex", pwd: "2dwe3dcd" }, { transaction: t} )
const group = Group.findOne( { name: "Admins", transaction: t} )
// etc.
})
Если происходит ошибка, транзакция автоматически откатывается.
Ответ 4
В приведенном выше коде есть ошибка в вызове destroy.
await Model.destroy({where: {id}, transaction});
Транзакция является частью объекта options.
Ответ 5
Для тестового покрытия решения user7403683, описанного выше, рассмотрите использование sequelize-mock
и sinon
:
import SequelizeMock from 'sequelize-mock';
import sinon from 'sinon';
sandbox.stub(models, 'sequelize').returns(new SequelizeMock());
для успешных транзакций:
sandbox.stub(model.sequelize, 'transaction')
.resolves({commit() {}});
and stub everything in the transaction block
commit() {} provides stubbing of transaction.commit(),
otherwise you'll get a "method does not exist" error in your tests
или неудачная транзакция:
sandbox.stub(models.sequelize, 'transaction').resolves({rollback() {}});
to cover transaction.rollback()
проверить логику catch()
.
Ответ 6
async () => {
let t;
try {
t = await sequelize.transaction({ autocommit: true});
let _user = await User.create({}, {t});
let _userInfo = await UserInfo.create({}, {t});
t.afterCommit((t) => {
_user.setUserInfo(_userInfo);
// other logic
});
} catch (err) {
throw err;
}
}
Ответ 7
Если CLS включен в вашем проекте, Sequelize может использовать его для сохранения объекта транзакции и передачи его всем запросам в цикле continuation-passing
.
Установка:
import { Sequelize } from "sequelize";
import { createNamespace } from "cls-hooked"; // npm i cls-hooked
const cls = createNamespace("transaction-namespace"); // any string
Sequelize.useCLS(cls);
const sequelize = new Sequelize(...);
Использование:
const removeUser = async (id) => {
await sequelize.transaction(async () => { // no need 'async (tx)'
await removeClasses(id);
await User.destroy({ where: { id } }); // will auto receive 'tx'
});
}
const removeClasses = async (userId) => {
await UserClass.destroy({ where: { userId } }); // also receive the same transaction object as this function was called inside 'sequelize.transaction()'
await somethingElse(); // all queries inside this function also receive 'tx'
}
Как это работает?
Из исходного кода Sequelize: github.com/sequelize
Проверьте и сохраните транзакцию в CLS
if (useCLS && this.sequelize.constructor._cls) {
this.sequelize.constructor._cls.set('transaction', this);
}
Извлечь транзакцию из CSL и установить параметры
if (options.transaction === undefined && Sequelize._cls) {
options.transaction = Sequelize._cls.get('transaction');
}
Подробнее: