Node.js 7 how to use sequelize transaction with async / await?

Transactionssequelize.js

Transactions Problem Overview


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

Transactions Solutions


Solution 1 - Transactions

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

Solution 2 - Transactions

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.

Solution 3 - Transactions

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.

Solution 4 - Transactions

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:

  1. Sequelize: automatically pass transactions to all queries
  2. CLS hooked
  3. Async Hooks

Solution 5 - Transactions

The above code has an error in destroy call.

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

Transaction is part of the options object.

Solution 6 - Transactions

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

Solution 7 - Transactions

//try with this

const transaction = await sequelize.transaction({ autocommit: false });
    try {
      await Model.create(data, {transaction})
    } catch (e) {
      if (transaction) await transaction.rollback();
      next(e);
      response.status(500).json({ error: e });
    }

    if (transaction) {
      await transaction.commit();
    }

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionqiushijieView Question on Stackoverflow
Solution 1 - Transactionsuser7403683View Answer on Stackoverflow
Solution 2 - TransactionskosinixView Answer on Stackoverflow
Solution 3 - TransactionsrlibView Answer on Stackoverflow
Solution 4 - TransactionsThoView Answer on Stackoverflow
Solution 5 - TransactionsSuhail AnsariView Answer on Stackoverflow
Solution 6 - TransactionsSuperCView Answer on Stackoverflow
Solution 7 - TransactionsEdgar RPView Answer on Stackoverflow