Мы используем Knex SQL Query Builder для выполнения транзакции ACID в Node, и мы испытываем странное поведение при использовании цикла с Knex. Приведенный ниже код принимает массив таблиц и затем условно выполняет вставку или обновление. Первая таблица - это transactionHeader, и она обрабатывается первой. Затем строки в таблице transactionDetail обрабатываются в рамках всей транзакции. Новые ключи (rowids) накапливаются в массиве rowids.
ПРОБЛЕМА. Основная проблема заключается в том, что кажется невозможным выйти из цикла в processTransactionDetail (), если Knex вернул ошибку. Ни throw, ни return не выйдут из цикла или функции. Это означает, что в случае ошибки при обработке transactionDetail он продолжит обработку оставшихся строк перед выходом.
let rowids: any[] = [];
knex.transaction(function(trx) {
// Process transactionHeader
if (transactionHeader.rowid) {
// Update transactionHeader
trx('transaction')
.transacting(trx)
.update(transactionHeader)
.where('rowid', transactionHeader.rowid)
.then(function(transactionrowid) {
rowids.push({ table: 'transaction', rowid: transactionHeader.rowid });
// Update transactionDetail rows.
processTransactionDetail(transactionrowid, trx);
})
.catch(trx.rollback);
} else {
// Insert transactionHeader
trx('transaction')
.transacting(trx)
.insert(transactionHeader, 'rowid')
.then(function(transactionrowid) {
rowids.push({ table: 'transaction', rowid: transactionrowid });
// Insert transactionDetail rows.
processTransactionDetail(transactionrowid, trx);
})
.catch(trx.rollback);
}
}).then(function(inserts) {
console.log('success!', rowids)
callback(null, { success: true }, { data: rowids })
return;
}).catch(function(error) {
console.error('error', error);
callback(null, {
success: false,
message: error.message
}, { data: rowids })
return;
});
/*
* Process transactionDetail rows.
*/
function processTransactionDetail(transactionHeaderRowID: number, trx) {
var promise: any;
let table: TABLE;
let rowid: number;
for (let i = 1; i < tablesToProcess.length; i++) {
table = tablesToProcess[i];
rowid = table.data[0].rowid;
// Update
if (rowid) {
for (let row = 0; row < table.data.length; row++) {
promise = knex(table.name)
.transacting(trx)
.update(table.data[row])
.where('rowid', rowid)
.then(function(rowid) {
rowids.push({ table: table.name, rowid: rowid });
.catch(function(error) {
// --------------------------------
// **PROBLEM**: THERE IS NO WAY TO BREAK FROM THE LOOP
// --------------------------------
throw 'error';
return;
// --------------------------------
})
}
// Insert
} else {
for (let row = 0; row < table.data.length; row++) {
promise = knex(table.name)
.transacting(trx)
.insert(table.data[row])
.then(function(rowid) {
rowids.push({ table: table.name, rowid: rowid });
})
.catch(function(error) {
// --------------------------------
// **PROBLEM**: THERE IS NO WAY TO BREAK FROM THE LOOP
// --------------------------------
throw 'error';
return;
// --------------------------------
});
}
}
}
promise.then(function(x) {
promise.then(trx.commit);
});
}
ОБНОВЛЕНО: Это правильная структура? Не уверен, что обработчики ошибок внизу действительно нужны.
knex.transaction(function(trx) {
// Update Row
function updateRow(table, rowid, row) {
return knex(table.name)
.transacting(trx)
.update(row)
.where('rowid', rowid)
.then(function(rowid) {
rowids.push({
table: table.name,
rowid: rowid
});
});
}
// Insert Row
function insertRow(table, rowid, row) {
return knex(table.name)
.transacting(trx)
.insert(row)
.then(function(rowid) {
rowids.push({
table: table.name,
rowid: rowid
});
});
}
// Process Tables
Promise.mapSeries(tablesToProcess, function(table) {
let rowid = table.data[0].rowid;
// choose the right function to apply to the rows
var fn = rowid ? updateRow : insertRow;
// fn needs table and rowid
fn = fn.bind(this, table, rowid);
// Process Rows
return Promise.mapSeries(table.data, fn)
.then(function(result) {
// result is an array with all the knex promises result
return result;
}).catch(function(err) {
console.log('an error happened');
//trx.rollback(); // QUESTION: IS THIS NEEDED?
throw err; // IS THERE A WAY TO
});
}).then(function(result) {
console.log('success', result);
trx.commit();
// callback(null, { success: true }, { data: rowids })
// return;
}).catch(function(error) {
console.log('error', error);
trx.rollback();
callback(null, { success: false, message: error.message }, { data: rowids })
});
}).then(function(inserts) {
console.log('success!', rowids)
callback(null, { success: true }, { data: rowids })
}).catch(function(error) {
console.log('error', error);
callback(null, { success: false, message: error.message }, { data: rowids })
});