Idb-keyval — это легковесная обёртка над IndexedDB,
которая упрощает работу с асинхронным хранилищем ключ-значение. Основная
задача библиотеки — предоставить удобный интерфейс для операций
get, set, del и
clear без необходимости писать сложный код с транзакциями и
открытием базы данных.
Однако при работе с несколькими вкладками браузера возникают вопросы конкурентного доступа. IndexedDB обеспечивает атомарность транзакций, но не обеспечивает автоматической блокировки между вкладками. Это означает, что два скрипта в разных вкладках могут одновременно читать и записывать одно и то же значение, что может привести к гонкам данных.
Каждая операция set или del в
idb-keyval фактически создаёт отдельную транзакцию с
режимом readwrite. Транзакции в IndexedDB атомарны: либо
все изменения внутри транзакции фиксируются, либо откатываются.
Пример записи:
import { set } from 'idb-keyval';
await set('user', { name: 'Alice', age: 25 });
Если два вызова set('user', {...}) происходят
одновременно в разных вкладках, конечное значение будет зависеть от
того, какая транзакция завершится последней. Библиотека не
предотвращает потерю данных, если параллельные операции
выполняются на одном ключе.
Основные сценарии гонки данных:
Одновременное обновление одного ключа
Удаление ключа во время чтения
Для управления конкурентным доступом можно использовать несколько подходов:
Можно использовать localStorage или
BroadcastChannel для уведомления других вкладок о начале
или завершении изменения:
const channel = new BroadcastChannel('idb-keyval-sync');
async function safeSet(key, value) {
channel.postMessage({ key, action: 'start' });
await set(key, value);
channel.postMessage({ key, action: 'end' });
}
channel.onmess age = (event) => {
const { key, action } = event.data;
if (action === 'start') {
console.log(`Ключ ${key} занят другой вкладкой`);
}
};
Этот метод не блокирует запись на уровне IndexedDB, но позволяет реализовать мягкие блокировки и предупредить гонки.
Можно хранить для каждого ключа поле version и проверять
его при записи:
const data = await get('settings') || { value: {}, version: 0 };
data.value.theme = 'dark';
data.version++;
await set('settings', data);
Если другая вкладка изменила version между чтением и
записью, изменения можно откатить или повторить с актуальной версией.
Это эмулирует оптимистичную блокировку.
Для операций, которые должны быть атомарны внутри одной вкладки,
можно использовать транзакции напрямую через idb (основу
idb-keyval) и объединять несколько ключей в одной
транзакции. Пример:
import { openDB } from 'idb';
const db = await openDB('app-db', 1, {
upgrade(db) {
db.createObjectStore('keyval');
}
});
await db.transaction('keyval', 'readwrite', async tx => {
const store = tx.objectStore('keyval');
const valueA = await store.get('a');
const valueB = await store.get('b');
store.put(valueA + valueB, 'c');
});
Здесь операции на ключах a, b,
c выполняются атомарно внутри одной транзакции, что
предотвращает неконсистентность в пределах одной
вкладки, но не между вкладками.
storage при записи в той же вкладке.Idb-keyval обеспечивает удобный доступ к IndexedDB, но
не предоставляет встроенной блокировки между вкладками.
Контроль за конкурентным доступом необходимо реализовывать на уровне
приложения через:
BroadcastChannel),Только сочетание этих подходов позволяет безопасно работать с общими данными в нескольких вкладках без потери информации и гонок данных.