Транзакция в Dexie.js представляет собой атомарную единицу работы с IndexedDB, в рамках которой несколько операций выполняются как единое целое. Это означает, что либо все изменения успешно фиксируются, либо в случае ошибки откатываются полностью.
В основе Dexie.js лежит нативный IndexedDB API, где транзакции
являются обязательным механизмом доступа к данным. Dexie упрощает их
использование, предоставляя высокоуровневый интерфейс
db.transaction(), который делает управление потоками данных
более предсказуемым и структурированным.
Ключевые свойства транзакций:
db.transaction()Базовая форма объявления транзакции в Dexie.js:
db.transaction(mode, tables, callback)
mode — режим доступа:
"r" (read)"rw" (readwrite)tables — список таблиц (stores), участвующих в транзакции
callback — функция, содержащая операции с базой
Пример:
await db.transaction("rw", db.users, db.orders, async () => {
await db.users.add({ id: 1, name: "Alex" });
await db.orders.add({ id: 100, userId: 1, total: 250 });
});
"r")Используется для безопасного получения данных без модификации состояния базы.
await db.transaction("r", db.users, async () => {
const allUsers = await db.users.toArray();
console.log(allUsers);
});
Особенности:
"rw")Используется при необходимости изменения данных.
await db.transaction("rw", db.users, async () => {
const user = await db.users.get(1);
user.lastLogin = Date.now();
await db.users.put(user);
});
Особенности:
Все обращения к IndexedDB внутри db.transaction()
автоматически привязываются к текущей транзакции, если они происходят в
рамках её асинхронного контекста.
await db.transaction("rw", db.users, async () => {
const u1 = await db.users.get(1);
const u2 = await db.users.get(2);
await db.users.put({ ...u1, active: true });
await db.users.put({ ...u2, active: true });
});
Важно:
Dexie.js автоматически откатывает транзакцию при возникновении исключения.
await db.transaction("rw", db.users, async () => {
await db.users.add({ id: 1, name: "John" });
throw new Error("Ошибка");
await db.users.add({ id: 2, name: "Jane" });
});
Результат:
Одним из ключевых преимуществ Dexie.js является возможность объединять несколько таблиц в одну транзакцию.
await db.transaction("rw", db.users, db.logs, db.sessions, async () => {
const user = await db.users.get(1);
await db.sessions.add({ userId: user.id, started: Date.now() });
await db.logs.add({ message: "User login", userId: user.id });
});
Преимущества:
Dexie.js поддерживает вложенные транзакции, однако они не создают новые независимые транзакции, а присоединяются к текущей.
await db.transaction("rw", db.users, async () => {
await updateUser(1);
});
async function updateUser(id) {
const user = await db.users.get(id);
user.visits++;
await db.users.put(user);
}
Особенность:
updateUser автоматически использует уже
активную транзакцию;db.transaction().Любая ошибка внутри callback приводит к отмене всей транзакции:
await db.transaction("rw", db.users, async () => {
await db.users.add({ id: 1 });
// Ошибка ключа
await db.users.add({ id: 1 });
});
Поведение:
Dexie.js завершает транзакцию автоматически при успешном выходе из callback.
Однако важно учитывать:
await;TransactionInactiveError.Пример корректного завершения:
await db.transaction("rw", db.users, async () => {
await db.users.put({ id: 1, name: "Updated" });
});
Promise.all внутри транзакцииПараллельные операции допустимы, если они относятся к одной транзакции.
await db.transaction("rw", db.users, async () => {
await Promise.all([
db.users.put({ id: 1, name: "A" }),
db.users.put({ id: 2, name: "B" })
]);
});
Особенности:
IndexedDB накладывает ряд ограничений, которые сохраняются в Dexie.js:
Если внутри транзакции выполняются тяжёлые вычисления или долгие асинхронные операции, транзакция может быть прервана браузером.
await db.transaction("rw", db.users, async () => {
await db.users.get(1);
await new Promise(resolve => setTimeout(resolve, 5000));
await db.users.put({ id: 1, name: "Late update" });
});
Риски:
TransactionInactiveError;Эффективное использование db.transaction() предполагает
минимизацию времени жизни транзакции и включение только необходимых
операций.
Пример корректной структуры:
await db.transaction("rw", db.users, db.logs, async () => {
const user = await db.users.get(1);
await db.users.put({ ...user, active: true });
await db.logs.add({ userId: user.id, event: "activate" });
});
Принципы:
Транзакция "rw" включает возможность чтения, поэтому
дополнительные "r" не требуются.
await db.transaction("rw", db.users, async () => {
const users = await db.users.toArray();
});
Особенности:
Dexie.js обеспечивает согласованность при одновременных операциях над несколькими таблицами.
await db.transaction("rw", db.accounts, db.transactions, async () => {
const acc = await db.accounts.get(1);
acc.balance -= 100;
await db.accounts.put(acc);
await db.transactions.add({
accountId: acc.id,
amount: -100
});
});
Результат:
IndexedDB автоматически управляет очередями транзакций:
Транзакция привязана к текущему асинхронному контексту. Потеря контекста приводит к ошибкам:
let t;
await db.transaction("rw", db.users, async tx => {
t = tx;
});
await db.users.get(1); // вне транзакции
Особенность:
tx нельзя использовать вне callback;db.transaction() в архитектуре Dexie.jsЯвные транзакции являются основным инструментом обеспечения целостности данных в Dexie.js. Они позволяют:
Использование db.transaction() формирует предсказуемую
модель работы с IndexedDB, где каждая операция имеет строго определённые
границы выполнения и гарантии согласованности данных.