Я выхожу на конечность и говорю, что db.serialize()
- это удобный метод, который не включает никакой магии. Должна быть возможность сериализации пакета операторов, ожидая, пока одно не завершится, перед отправкой следующего.
Это также будет работать для транзакций, единственное, что должно быть гарантировано , это то, что никакие другие записи не происходят с тем же объектом соединения db
во время выполнения операторов, поддерживать чистоту транзакции (как отмечалось в ветке обсуждения для node-sqlite3, выпуск # 304 ).
Цепочка будет выполняться путем строгого вызова следующего оператора в обратном вызове предыдущего, если только предыдущий не вернул ошибку, после чего выполнение должно быть остановлено.
Это неудобно, когда фактически укладывает обратные вызовы в исходном коде. Но если мы обещаем метод Database#run
, мы можем использовать обещания:
const sqlite3 = require('sqlite3');
sqlite3.Database.prototype.runAsync = function (sql, ...params) {
return new Promise((resolve, reject) => {
this.run(sql, params, function (err) {
if (err) return reject(err);
resolve(this);
});
});
};
Мы могли бы полагаться на util.promisify
для обещания, но это привело бы к потере одной детали обработки callback
в Database#run
(из документов * 1027) *):
Если выполнение было успешным, объект this
будет содержать два свойства с именами lastID
и changes
, которые содержат значение идентификатора последней вставленной строки и количество строк, затронутых этим запросом соответственно.
Наш пользовательский вариант захватывает объект this
и возвращает его в качестве результата обещания.
Таким образом, мы можем определить классическую цепочку обещаний, начиная с BEGIN
, затем связывая любое количество операторов с помощью Array#reduce
, и в конечном итоге вызывая COMMIT
в случае успеха или ROLLBACK
в случае ошибка:
sqlite3.Database.prototype.runBatchAsync = function (statements) {
var results = [];
var batch = ['BEGIN', ...statements, 'COMMIT'];
return batch.reduce((chain, statement) => chain.then(result => {
results.push(result);
return db.runAsync(...[].concat(statement));
}), Promise.resolve())
.catch(err => db.runAsync('ROLLBACK').then(() => Promise.reject(err +
' in statement #' + results.length)))
.then(() => results.slice(2));
};
Поскольку это создает цепочку обещаний, он также создает массив результатов операторов, которые он возвращает по завершении (минус два элемента в начале, первый - undefined
из Promise.resolve()
, второй - результат * * тысяча сорок-девять).
Теперь мы можем легко передать несколько операторов для сериализованного выполнения внутри неявной транзакции. Каждый член пакета может быть либо отдельным оператором, либо массивом с оператором и связанными параметрами (как ожидал бы Database#run
):
var statements = [
"DROP TABLE IF EXISTS foo;",
"CREATE TABLE foo (id INTEGER NOT NULL, name TEXT);",
["INSERT INTO foo (id, name) VALUES (?, ?);", 1, "First Foo"]
];
db.runBatchAsync(statements).then(results => {
console.log("SUCCESS!")
console.log(results);
}).catch(err => {
console.error("BATCH FAILED: " + err);
});
, который будет регистрировать что-то вроде этого
SUCCESS!
[ { sql: 'DROP TABLE IF EXISTS foo;', lastID: 1, changes: 1 },
{ sql: 'CREATE TABLE foo (id INTEGER NOT NULL, name TEXT);',
lastID: 1,
changes: 1 },
{ sql: 'INSERT INTO foo (id, name) VALUES (?, ?);',
lastID: 1,
changes: 1 } ]
В случае ошибки это приведет к откату, и мы получим сообщение об ошибке от механизма БД, плюс "в операторе #X" , где X относится к позиции оператора в партия.