Node.js 7 как использовать транзакцию selizeize с async/wait?

Node.js 7 и выше уже поддерживают синтаксис async/await. Как я должен использовать async/await с транзакциями sequelize?

Ответ 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');
}

Подробнее:

  1. Sequelize: автоматически передавать транзакции на все запросы
  2. CLS подключен
  3. Асинхронные Крючки