idb-keyval — это небольшая обёртка над IndexedDB,
упрощающая работу с ключ–значение хранилищем в браузере. Одной из
ключевых операций является функция update, позволяющая
модифицировать текущее значение по ключу атомарно. Под атомарностью
здесь подразумевается, что операция чтения, модификации и записи
выполняется как единый блок: промежуточные состояния недоступны другим
операциям.
Простейший пример использования update:
import { update } from 'idb-keyval';
update('counter', value => (value || 0) + 1)
.then(newValue => console.log('Обновлённое значение:', newValue));
В этом примере:
'counter'.(value || 0) + 1.На первый взгляд операция выглядит атомарной: никто не видит промежуточное состояние, и итоговое значение всегда корректно увеличено на 1.
Несмотря на обещание атомарности, idb-keyval не
превращает update в полноценную транзакцию на уровне всей
базы. Основные ограничения:
Ограничение на одну запись update
работает атомарно только для одного ключа. Если требуется обновление
нескольких ключей одновременно, никакой встроенной защиты от гонок нет:
два параллельных update на разные ключи могут пересекаться,
создавая потенциальные конфликты при логике, зависящей от нескольких
значений.
Нет блокировок на уровне базы IndexedDB
обеспечивает атомарность транзакций в пределах одной записи, но не
блокирует другие операции. Это значит, что параллельные вызовы
update на один ключ могут считывать одно и то же исходное
значение и одновременно его перезаписать. Результат может быть
неожиданным:
// Допустим, значение counter = 0
const increment = () => update('counter', v => (v || 0) + 1);
Promise.all([increment(), increment()]).then(console.log);
// Возможные результаты: 1, 1 или 2
Здесь два обновления могут сработать на одной и той же исходной базе
0, и оба записать 1, что ломает ожидаемую
атомарность.
Асинхронная природа операций update
возвращает Promise, но сама модификация делается в рамках
одной транзакции IndexedDB. Любые дополнительные операции до или после
update происходят асинхронно и не гарантируют
согласованности с другими параллельными обновлениями.
Сложные функции обновления Функция
преобразования может быть синхронной или асинхронной. Если функция
возвращает Promise, idb-keyval корректно ждёт
её завершения, но атомарность всё равно ограничена отдельным ключом:
update('userSettings', async settings => {
const newTheme = await fetchThemeFromServer();
return { ...settings, theme: newTheme };
});
Здесь update гарантирует, что для
userSettings промежуточного состояния не будет, но
параллельные вызовы могут привести к потерям обновлений.
update для простых
ключей: счётчики, флаги, небольшие объекты, где вероятность
параллельных изменений невелика.mutex на уровне Jav * aScript:let lock = Promise.resolve();
function lockedUpdate(key, fn) {
lock = lock.then(() => update(key, fn));
return lock;
}
update, особенно
при цепочках асинхронных операций, чтобы убедиться, что значение
изменилось ожидаемым образом.update внутри idb-keyvalreadwrite для выбранного
хранилища (store) IndexedDB.get(key), считывается текущее
значение.put(key, newValue).Важно отметить, что транзакция закрывается после завершения одной
записи. IndexedDB не предоставляет встроенной блокировки на уровне ключа
или базы, поэтому атомарность update следует рассматривать
как «атомарность отдельной транзакции для одного ключа», а не как
универсальную защиту от гонок.
update обеспечивает удобный способ изменять значения без
явного get и put, сокращая шаблон кода и
минимизируя риск ошибок. Однако его атомарность строго ограничена:
Для сценариев, где требуется атомарная обработка нескольких ключей
или предотвращение гонок при частых параллельных обновлениях,
idb-keyval нужно использовать совместно с внешними
механизмами синхронизации.