Транзакции в Dexie.js поддерживают механизм принудительного
прерывания выполнения через abort(), который позволяет
полностью откатить все изменения, сделанные внутри транзакции, и
перевести её в состояние отмены без частичного сохранения данных. Это
один из ключевых инструментов контроля целостности данных при работе с
IndexedDB, особенно в сценариях сложных цепочек операций, где ошибка на
любом этапе делает бессмысленным сохранение промежуточного
результата.
Транзакция в Dexie.js имеет чётко определённые жизненные состояния:
активная, завершённая и прерванная. Вызов abort() переводит
транзакцию в состояние прерывания и инициирует откат всех изменений,
сделанных в рамках текущего контекста.
После вызова:
Важно, что abort() влияет не только на текущую операцию,
но и на весь транзакционный контекст.
В Dexie.js транзакция создаётся через
db.transaction(...), а метод abort() доступен
через объект транзакции:
db.transaction('rw', db.users, db.orders, async (tx) => {
await db.users.add({ id: 1, name: 'Alice' });
if (someCriticalCondition) {
tx.abort();
}
await db.orders.add({ id: 100, userId: 1 });
});
В момент вызова tx.abort() выполнение транзакции
немедленно помечается как прерванное, и последующие операции либо не
выполняются, либо игнорируются системой отката.
Внутри транзакций Dexie.js прерывание может быть достигнуто двумя способами:
tx.abort() — явное программное прерывание;throw Error — аварийное завершение через
исключение.Разница проявляется в семантике:
abort():
throw:
Оба механизма приводят к откату транзакции, но abort()
предпочтителен, когда отмена является нормальной частью логики.
После вызова abort() транзакция переходит в состояние,
при котором:
await внутри транзакции завершаются с
ошибкой;onerror и onabort могут быть
вызваны на уровне транзакции.Пример наблюдаемого поведения:
db.transaction('rw', db.items, async (tx) => {
tx.on('abort', () => {
console.log('Транзакция отменена');
});
await db.items.add({ id: 1 });
tx.abort();
await db.items.add({ id: 2 }); // не выполнится
});
Часто транзакция прерывается при нарушении условий целостности:
db.transaction('rw', db.accounts, async (tx) => {
const acc = await db.accounts.get(1);
if (acc.balance < 0) {
tx.abort();
}
await db.accounts.update(1, { status: 'invalid' });
});
Если данные не проходят проверку, нет смысла сохранять частичные изменения:
db.transaction('rw', db.users, async (tx) => {
if (!isValid(userData)) {
tx.abort();
}
await db.users.add(userData);
});
При работе с несколькими store транзакция обеспечивает атомарность. abort() гарантирует откат всего набора операций:
db.transaction('rw', db.users, db.logs, async (tx) => {
await db.users.add(newUser);
await db.logs.add({ event: 'create_user' });
if (externalServiceFailed) {
tx.abort();
}
});
В Dexie.js транзакции основаны на промисах, и abort()
приводит к отклонению всех связанных промисов:
try {
await db.transaction('rw', db.items, async (tx) => {
await db.items.add({ id: 1 });
tx.abort();
await db.items.add({ id: 2 });
});
} catch (e) {
console.log(e.name); // AbortError
}
После abort() управление переходит в catch, а все
незавершённые операции считаются отменёнными.
IndexedDB обеспечивает атомарность на уровне транзакции, поэтому:
Однако важно учитывать, что чтение внутри транзакции до abort() всё ещё возвращает локальные изменения, если они были закешированы в рамках транзакции.
Если транзакции логически вложены или координируются через общий контекст, abort() верхнего уровня отменяет всё дерево операций:
db.transaction('rw', db.a, db.b, async (tx) => {
await db.a.add({ id: 1 });
db.transaction('rw', db.b, async (innerTx) => {
await db.b.add({ id: 2 });
tx.abort();
});
});
В этом случае прерывание внешней транзакции отменяет и внутренние операции.
Логика abort() строится вокруг принципа “всё или ничего”:
Это соответствует атомарной модели ACID, реализуемой через IndexedDB и обёртку Dexie.js, где abort() выступает механизмом принудительного отказа от commit на уровне движка хранения данных.