Node.js 7 and up already support async/await syntax. How should I use async/await with sequelize transactions?
6 Answers
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();
}
The accepted answer is an "unmanaged transaction", which requires you to call commit
and rollback
explicitly. For anyone who wants a "managed transaction", this is what it would look like:
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);
}
To rollback, just throw an error inside the transaction function:
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);
}
If any code that throws an error inside the transaction block, the rollback is automatically triggered.
The answer given by user7403683 describes async/await way for unmanaged transaction (http://docs.sequelizejs.com/manual/tutorial/transactions.html#unmanaged-transaction-then-callback-)
Managed transaction in async/await style may look as follows:
await sequelize.transaction( async t=>{
const user = User.create( { name: "Alex", pwd: "2dwe3dcd" }, { transaction: t} )
const group = Group.findOne( { name: "Admins", transaction: t} )
// etc.
})
If error occurs, the transaction is automatically rolled back.
If CLS is enabled, Sequelize can use that to keep your transaction object and automatically pass it to all queries inside the continuation-passing
cycle.
Setup:
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(...);
Usage:
const removeUser = async (id) => {
await sequelize.transaction(async () => { // no need `async (tx)`
await removeUserClasses(id);
await User.destroy({ where: { id } }); // will auto receive `tx`
});
}
const removeUserClasses = 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`
}
How it works?
From Sequelize source code: github.com/sequelize
Check and save transaction to CLS
if (useCLS && this.sequelize.constructor._cls) {
this.sequelize.constructor._cls.set('transaction', this);
}
Retrieve transaction from CLS and set to options
if (options.transaction === undefined && Sequelize._cls) {
options.transaction = Sequelize._cls.get('transaction');
}
Read more: