Node.js 7 how to use selizeize transaction with async / wait?

Node.js 7 and above already support async / await syntax. How should I use async / await with sequelize transactions?

+47
transactions
source share
7 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(); } 
+111
source share

The accepted answer is an “unmanaged transaction” that requires an explicit call to commit and rollback . For those who want a “managed transaction”, it looks like this:

 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 generate 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 throws an error inside the transaction block, the rollback automatically fires.

+32
source share

User response 7403683 describes an asynchronous / pending path for an unmanaged transaction ( http://docs.sequelizejs.com/manual/tutorial/transactions.html#unmanaged-transaction-then-callback- ).

An async / await-style transaction may look like this:

 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 an error occurs, the transaction is automatically rolled back.

+5
source share

There is an error in the destroy call in the above code.

  await Model.destroy({where: {id}, transaction}); 

A transaction is part of the options object.

+4
source share

For the test coverage of the user7403683 solution described above, consider using sequelize-mock and sinon :

 import SequelizeMock from 'sequelize-mock'; import sinon from 'sinon'; sandbox.stub(models, 'sequelize').returns(new SequelizeMock()); 

for successful transactions:

 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 

or failed transaction:

 sandbox.stub(models.sequelize, 'transaction').resolves({rollback() {}}); to cover transaction.rollback() 

check logic catch() .

0
source share
 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; } } 
0
source share

If CLS is included in your project, Sequelize can use it to save the transaction object and pass it to all requests in a continuation-passing loop.

Installation:

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

Using:

 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' } 

How it works?

From Sequelize source code: github.com/sequelize

Verify and save the transaction in CLS

 if (useCLS && this.sequelize.constructor._cls) { this.sequelize.constructor._cls.set('transaction', this); } 

Extract transaction from CSL and set parameters

 if (options.transaction === undefined && Sequelize._cls) { options.transaction = Sequelize._cls.get('transaction'); } 

More details:

  1. Sequelize: automatically transfer transactions to all requests
  2. CLS connected
  3. Asynchronous Hooks
0
source share

All Articles