Ошибки и откат транзакции внутри update

Библиотека idb-keyval предоставляет удобный интерфейс для работы с IndexedDB в виде простых функций get, set, del, update и других. Особое внимание заслуживает функция update, которая позволяет изменять значение по ключу, основываясь на текущем состоянии. Понимание обработки ошибок и возможности отката транзакции в update критично для надёжного управления состоянием.


Механизм работы update

Функция update принимает два аргумента:

update(key, updater)
  • key — ключ в IndexedDB.
  • updater — функция, которая принимает текущее значение и возвращает новое.

Пример базового использования:

import { update } from 'idb-keyval';

await update('counter', value => (value || 0) + 1);

В этом примере update получает текущее значение counter, увеличивает его на 1 и сохраняет обратно.


Обработка ошибок внутри update

update выполняется в рамках транзакции IndexedDB. Любая ошибка, возникшая внутри функции updater, приводит к откату транзакции. Это ключевое отличие от использования get + set вручную, где ошибки могут оставить базу в частично изменённом состоянии.

Пример с выбрасыванием ошибки:

await update('user', value => {
    if (!value) throw new Error('Пользователь не найден');
    return { ...value, active: true };
}).catch(err => {
    console.error('Ошибка обновления:', err);
});

В этом случае:

  • Если значение по ключу отсутствует, будет выброшена ошибка.
  • Транзакция автоматически откатится, и данные не изменятся.
  • Можно безопасно обрабатывать исключение с помощью .catch или try/catch.

Поведение при асинхронных операциях в updater

Функция updater не поддерживает асинхронный код напрямую. Любое использование await внутри неё приведёт к ошибке или некорректному результату. IndexedDB транзакции синхронны на уровне обработки изменений данных, поэтому весь код внутри updater должен быть синхронным.

Неправильный пример:

await update('settings', async value => {
    const response = await fetch('/config'); // ❌ не сработает
    return { ...value, config: await response.json() };
});

Правильный подход — сначала получить необходимые данные, затем выполнить update синхронно:

const response = await fetch('/config');
const config = await response.json();

await update('settings', value => ({ ...value, config }));

Откат транзакции

Если внутри update выбрасывается ошибка:

  1. IndexedDB отменяет все изменения, сделанные в этой транзакции.
  2. Значение по ключу остаётся неизменным.
  3. Ошибка может быть перехвачена и обработана.

Это гарантирует атомарность операции, что особенно важно при работе с комплексными структурами данных.


Примеры сложного отката

Множественные проверки

await update('cart', cart => {
    if (!cart.items.length) throw new Error('Корзина пуста');
    if (cart.total > 1000) throw new Error('Превышен лимит суммы');
    return { ...cart, checkedOut: true };
}).catch(err => console.log(err.message));

В этом примере любое нарушение условий прерывает обновление, и состояние корзины остаётся прежним.

Валидация и откат

await update('profile', profile => {
    if (!profile.email.includes('@')) throw new Error('Некорректный email');
    return { ...profile, verified: true };
});

Ошибки валидации автоматически откатывают изменения, что предотвращает сохранение некорректных данных.


Практические рекомендации

  • Сохранять синхронность updater — все вычисления должны быть быстрыми и синхронными.
  • Использовать исключения для отката — выброс ошибки внутри updater безопасно прерывает транзакцию.
  • Проверять наличие данных перед изменением — это уменьшает вероятность непредвиденного отката.
  • Обрабатывать ошибки извне — через .catch или try/catch для логирования и пользовательских уведомлений.

Функция update в idb-keyval сочетает в себе простоту записи и атомарность транзакций. Понимание поведения при ошибках и правильная организация кода внутри updater позволяют безопасно управлять данными и предотвращать неконсистентность в IndexedDB.