Почему версия async / await моего транзакционного приложения mssql не работает, но версия обратного вызова работает?

Чтобы прояснить проблему, с которой у меня работает приложение nodejs / mssql, я попытался закодировать две функционально эквивалентные версии простого (подготовленного) оператора INSERT, заключенного в транзакцию.

Версия обратных вызовов работает - вставляет строку в мою базу данных Sql Server.

Версия async / await выдает ошибку -

TransactionError: Can't commit transaction. There is a request in progress.

Я пробовал много вариантов неудачной версии (переупорядочивание операторов там, где это возможно), но версия, включенная ниже, является версией, которая наиболее точно имитирует логику работающей версии обратного вызова.

Спасибо!

var sql = require('mssql');  // mssql: 4.1.0; tedious: 2.2.4; node: v8.4.0

var cfg = {
    "db": "sqlserver",
    "domain": "XXXXXX",
    "user": "cseelig",
    "password": "xxxxxx",
    "server": "xxxxxx.xxxxxx.xxxxxx.xxxxxx",
    "port": 1433,
    "stream": false,
    "options": { 
        "trustedConnection": true
    },
    "requestTimeout": 900000,
    "connectionTimeout": 30000,
    "pool": {
        "max": 3,
        "min": 0,
        "idleTimeoutMillis": 30000
    }
};

var statement = "insert into wng_dw.dbo.D_LIB_Google_Search_Query (query, LastUpdateDate) values (@query, GetDate())";

// I only run one or the other -

main1("12347");   // fails
main2("98765:);   // works

async function main1(val) {

    try {
        const conn = await new sql.connect(cfg);
        const transaction = new sql.Transaction();
        await transaction.begin();
        const ps = new sql.PreparedStatement(transaction);
        ps.input('query', sql.VarChar(200));
        await ps.prepare(statement);
        await ps.execute( {"query": val} );
        await ps.unprepare();
        await transaction.commit();
        sql.close;
    } catch(err){
        console.log("Error: " + err);
    };

    process.exit(0);

}


async function main2(val) {

    sql.connect(cfg, err => {
        const transaction = new sql.Transaction();
        transaction.begin(err => {
            const ps = new sql.PreparedStatement(transaction);
            ps.input('query', sql.VarChar(200));
            ps.prepare(statement, err => {
                ps.execute( {"query": val}, (err, result) => {
                    ps.unprepare(err => { 
                        transaction.commit(err => {
                            sql.close();
                        });
                    });
                });
            });
        });
    });

}

person CSeelig    schedule 13.02.2018    source источник
comment
ps.prepare() и ps.execute() возвращают обещание? К вашему сведению, вы делаете await ps.unprepare, что, вероятно, должно быть ps.unprepare(). await ждет чего-то, только если ожидает обещания.   -  person jfriend00    schedule 14.02.2018
comment
Если я не жду ps.prepare (), то: Ошибка: PreparedStatementError: Оператор не подготовлен. Сначала вызовите prepare (). Что касается unprepare (), согласитесь! Я исправил и протестировал; продолжает терпеть неудачу как исходный (запрос в процессе).   -  person CSeelig    schedule 14.02.2018
comment
Кроме того, при переводе обратных вызовов в awaits я ожидаю следующего: если метод использует обратные вызовы в оригинале, то он должен вернуть обещание в ревизии. Это звук?   -  person CSeelig    schedule 14.02.2018
comment
Я не знаю, что означает возврат обещания в ревизии. Вы никогда не можете предположить, что знаете, что что-то возвращается. Вы либо смотрите в документ, либо в код, чтобы увидеть, что возвращает функция. Какую конкретную версию node-msql вы используете? И изучали ли вы документ или код для различных функций, которые вы используете, чтобы узнать, возвращают ли они обещания?   -  person jfriend00    schedule 14.02.2018
comment
Mea culpa - я признаю лень в своих выводах относительно того, как это должно работать. Версии компонентов: mssql: 4.1.0; утомительно: 2.2.4; узел: v8.4.0   -  person CSeelig    schedule 14.02.2018


Ответы (2)


Transaction.begin не возвращает обещание. Вы могли просто его пообещать. Примерно так:

await new Promise(resolve => transaction.begin(resolve));
const request = new sql.Request(transaction);
//...
await transaction.commit();

После фиксации и отката объект «запрос» больше не мог использоваться. В противном случае будет отображаться ошибка, касающаяся того, что транзакция не началась ....

Надеюсь на эту помощь.

person cscan    schedule 15.03.2018
comment
Это должен быть принятый ответ - прекрасно работает @cscan - person ttemple; 05.10.2019
comment
На момент написания transaction.begin действительно возвращает обещание при условии, что функция обратного вызова не предусмотрена, например. в этом случае begin() возвращает обещание - const t = await transaction.begin(ISOLATION_LEVEL.READ_COMMITTED) - person firxworx; 15.01.2021

Прежде чем вы сможете зафиксировать или откат транзакцию, все операторы не должны быть подготовлены.

Вы также должны await оператор unprepare, иначе запрос все еще обрабатывается, а выполнение обещания еще не выполнено.

Используйте небольшую обертку, чтобы упростить задачу:

import * as dotenv from 'dotenv'
import mssql from 'mssql'

dotenv.config()

const sqlServerConfig = {
  server: process.env.SQL_SERVER,
  user: process.env.QS_USER,
  password: process.env.QS_PASS,
  options: { enableArithAbort: false },
}

let pool: mssql.ConnectionPool
let transaction: mssql.Transaction
const statements: mssql.PreparedStatement[] = []

export const connect = async (): Promise<void> => {
  pool = new mssql.ConnectionPool({ ...sqlServerConfig, database: process.env.DATABASE })
  await pool.connect()
}

export const disconnect = async (): Promise<void> => {
  if (typeof pool == 'undefined') return
  if (pool.connected) await pool.close()
}

export const begin = async (): Promise<void> => {
  transaction = new mssql.Transaction(pool)
  await transaction.begin()
}

export const unprepare = async (statement: mssql.PreparedStatement): Promise<void> => {
  if (typeof statement == 'undefined') return
  if (statement.prepared) await statement.unprepare()
}

export const commit = async (): Promise<void> => {
  await transaction.commit()
}

export const rollback = async (): Promise<void> => {
  for (const statement of statements) {
    await unprepare(statement)
  }
  if (typeof transaction == 'undefined') return
  await transaction.rollback()
}

export const createStatement = (): mssql.PreparedStatement => {
  const statement = new mssql.PreparedStatement(transaction)
  statements.push(statement)
  return statement
}

Использование:

try {
  await connect()
  await begin()

  const myStatement = createStatement()

  ..... bind parameters
  ..... prepare statement

  for ( ..... ) {
    await myStatement.execute( ..... )
  }

  await unprepare(myStatement)

  await commit()
  await disconnect()
  exit(0)
}
catch(e) {
  log.error(e)
  await rollback()
  await disconnect()
  exit(1)
}

Вы создаете подготовленный оператор с помощью createStatement (). createStatement отслеживает операторы, поэтому в случае отката они не будут подготовлены для вас, когда вы вызовете откат.

person Phierru    schedule 13.02.2021